SQL Tuning
March 24, 2003 - 12pm Central time zone
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.
Difference in SQL's
August 21, 2003 - 7am Central time zone
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 August 21, 2003 - 6pm Central time zone:
nope.
did you try benchmarking them. I could, but it seems you already have everything setup?
use tkprof
sql re-write suggestion
October 10, 2003 - 4pm Central time zone
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 October 10, 2003 - 5pm Central time zone:
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
/
excellent explanation
November 3, 2003 - 10am Central time zone
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
:-)
heres my set autotrace on
November 7, 2003 - 11am Central time zone
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 November 7, 2003 - 2pm Central time zone:
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")

November 8, 2003 - 9pm Central time zone
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 November 9, 2003 - 7am Central time zone:
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?
excellent response I did not notice this
November 12, 2003 - 3pm Central time zone
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 November 12, 2003 - 4pm Central time zone:
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;
Optimal Schema Design
December 4, 2003 - 6pm Central time zone
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 December 4, 2003 - 8pm Central time zone:
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.
Sql's across db links.
December 5, 2003 - 6am Central time zone
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
SQL tuning the scientific way
January 1, 2004 - 2pm Central time zone
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 January 1, 2004 - 5pm Central time zone:
I believe 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.
if you run a query 1,000,000 times in a loop -- it'll still be slow even if you make it run 2x as
fast. You want to look at the algorithms and change them so you don't do something 1,000,000 times
(use bulk SQL)...
Reducing I/O
January 3, 2004 - 5pm Central time zone
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 January 3, 2004 - 6pm Central time zone:
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)
Tuning SQL Query
January 7, 2004 - 2am Central time zone
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 January 7, 2004 - 7am Central time zone:
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.
index range scan related issue
January 7, 2004 - 1pm Central time zone
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 January 7, 2004 - 6pm Central time zone:
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"
follow up to earlier
January 8, 2004 - 7am Central time zone
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 January 8, 2004 - 1pm Central time zone:
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.
one correction
January 8, 2004 - 7am Central time zone
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 January 8, 2004 - 1pm Central time zone:
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)
Oops
January 8, 2004 - 7am Central time zone
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
SQL Tuning
January 8, 2004 - 3pm Central time zone
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
Kiran from India
January 28, 2004 - 12am Central time zone
Reviewer: Kiran from India
Hi,
Tom Could u pls suggest me a good bood for SQL Tuning.
Followup January 28, 2004 - 8am Central time zone:
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.
tuning tools are much more clever than CBO!
January 29, 2004 - 3am Central time zone
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?!
Oracle should hire Quest to write their CBO
January 30, 2004 - 7am Central time zone
Reviewer: A reader
I think so, Quest SQL LAB seems much better than CBO!
Followup January 30, 2004 - 8am Central time zone:
you should give OEM a try as well -- it does the same thing.
Oracle should hire Quest?
January 30, 2004 - 9am Central time zone
Reviewer: Mark A. Williams from Indianapolis, IN USA
Funny. Did the tool suggest a change to the query?
- Mark
Quest Lab Better Than CBO ???
January 30, 2004 - 11am Central time zone
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 January 30, 2004 - 7pm Central time zone:
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?
any thoughts on this query
March 12, 2004 - 12pm Central time zone
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 March 12, 2004 - 5pm Central time zone:
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)
Thank You very much !!
March 15, 2004 - 9am Central time zone
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 March 15, 2004 - 9am Central time zone:
from ( (selec
^^ remove
Identifying Full Table Scan
April 12, 2004 - 12pm Central time zone
Reviewer: Vivek Sharma from Bombay, India
Dear Tom,
I was going through your book "Effective Oracle by Design". On page no.104 you have done following
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));
create unique index IDX_MAP on MAP(i1, i2);
Now 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';
The plan showed a full table scan of MAP. You commented "Is there any way to avoid the full table
scan on the Map table ? Whatever I try, one table is always going for a full scan. What should I do
to avoid a full scan in such case ?"
I do agree that full scans are not always evill indexes are not always good. But would like to know
that how can you say so confidently that whatever, keep aside hints, we do there is no way to
avoid a Full table scan in the above query. Would like to know for my knowledge purpose.
Thanks and Regards
Vivek Sharma
Followup April 12, 2004 - 3pm Central time zone:
Look at the existing structures.
Look at the predicates.
there is a predicate "i1.v = 'x'". there is no index on i1(v)
there is a predicate "i2.v = 'y'". there is no index on i2(v)
so, if we lead with either of i1 or i2 -- we must full scan them since there is no index in place
to be used.
So, we are left with "what if we drive with map". Well, we have NO predicates on map really --
just a join condition. Sure, map(i1,i2) is indexed but so what? We'd have to full scan I1 to find
values to join to MAP (so we'd be back to driving with i1 again)
Just look at the indexes that are there, the query itself and tell me how to avoid an index. The
question rules them out given the existing structures.
Do you have a better way for this query?
April 14, 2004 - 2pm Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
We have an existing application that runs under SQL Server. I am now converting the DB part into
Oracle. I can't change table strucatures. Here is the piece of SQL, I would like to see whether or
not you have a better way:
CREATE TABLE test
(tcn VARCHAR2(13) NOT NULL,
alias_field VARCHAR2(25),
value VARCHAR2(100));
INSERT INTO test VALUES ('A', 'NAM', 'JOHN');
INSERT INTO test VALUES ('A', 'DOB', '12/31/1959');
INSERT INTO test VALUES ('B', 'NAM', 'JEN');
INSERT INTO test VALUES ('C', 'DOB', '01/12/1945');
INSERT INTO test VALUES ('D', null, null);
SELECT * from test;
TCN ALIAS_FIEL VALUE
------------- ---------- --------------------
A NAM JOHN
A DOB 12/31/1959
B NAM JEN
C DOB 01/12/1945
D
This 5 records cover all possible scenarios:
nam dob
------------------------
not null not null
not null null
null not null
null null
The query should return the following results:
tcn nam dob
--------------------------
A JOHN 12/31/1959
B JEN
C 01/12/1945
D
Here is what I wrote, but I don't think I can use this piece because the real table is big:
SELECT n1.tcn, n1.VALUE nam, n2.VALUE dob
FROM test n1,
test n2
WHERE n1.tcn = n2.tcn
AND n1.alias_field = 'NAM'
AND n2.alias_field = 'DOB'
UNION ALL
SELECT n1.tcn, NULL nam, n1.VALUE dob
FROM test n1
WHERE n1.alias_field = 'DOB'
AND EXISTS (SELECT n2.tcn
FROM test n2
WHERE n2.tcn = n1.tcn
GROUP BY n2.tcn
HAVING count(n2.tcn)=1)
UNION ALL
SELECT n1.tcn, n1.VALUE nam, NULL dob
FROM test n1
WHERE n1.alias_field = 'NAM'
AND EXISTS (SELECT n2.tcn
FROM test n2
WHERE n2.tcn = n1.tcn
GROUP BY n2.tcn
HAVING count(n2.tcn)=1)
UNION ALL
SELECT n1.tcn, NULL nam, NULL dob
FROM test n1
WHERE n1.alias_field IS NULL
AND EXISTS (SELECT n2.tcn
FROM test n2
WHERE n2.tcn = n1.tcn
GROUP BY n2.tcn
HAVING count(n2.tcn)=1);
Would you please help?
Thank you.
Followup April 14, 2004 - 3pm Central time zone:
ugh, the funky data model.
so flexible, yet so non-performant and inflexible to query.
If you want to see my real opinion of this - i spent a couple of pages on "why you don't want to go
here" in "Effective Oracle by Design".
But here you go:
ops$tkyte@ORA9IR2> select tcn,
2 max(decode(alias_field,'NAM',value)) nam,
3 max(decode(alias_field,'DOB',value)) dob
4 from test
5 group by tcn;
TCN NAM DOB
------------- --------------- ---------------
A JOHN 12/31/1959
B JEN
C 01/12/1945
D
Thank You
April 14, 2004 - 8pm Central time zone
Reviewer: Jennifer Chen
Wow, that sounds so easy for you. I showed your response to my boss, and he was impressed.
Unfortunately, he can't pay you :), but he did buy all your books for me last year.
I will read the pages you mentioned here and will try to design a better data model when I get the
opportunities.
Thanks for your prompt response and help.
Can you do better
April 20, 2004 - 12pm Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
I have a simple query that joins 6 big tables:
SQL> set autotrace traceonly
SQL> set timing on
SQL> SELECT SID, fbi, last_name || ',' || first_name || middle_name,
2 iii_status, sex, dob, rac, iffs
3 FROM alias.cch_person c,
4 alias.name_index n,
5 alias.name_dob d,
6 alias.name_sex s,
7 alias.name_rac r
8 WHERE status_code = 'A'
9 AND enter_date_time < TO_DATE ('11/11/1951 12:00', 'MM/DD/YYYY HH24:MI')
10 AND c.mpi_number = n.mpi_number
11 AND n.name_type_code = 'B'
12 AND n.mpi_number = d.mpi_number
13 AND d.mpi_number = s.mpi_number
14 AND s.mpi_number = r.mpi_number
15 AND d.primary_value_flag = 'Y'
16 AND s.primary_value_flag = 'Y'
17 AND r.primary_value_flag = 'Y'
18 ORDER BY SID;
59451 rows selected.
Elapsed: 00:01:28.01
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=13661 Card=68056 Byt
es=6261152)
1 0 SORT (ORDER BY) (Cost=13661 Card=68056 Bytes=6261152)
2 1 HASH JOIN (Cost=11092 Card=68056 Bytes=6261152)
3 2 HASH JOIN (Cost=9673 Card=68056 Bytes=5648648)
4 3 HASH JOIN (Cost=8269 Card=68056 Bytes=5036144)
5 4 HASH JOIN (Cost=7007 Card=102646 Bytes=5953468)
6 5 TABLE ACCESS (FULL) OF 'CCH_PERSON' (Cost=3058 C
ard=81485 Bytes=2770490)
7 5 TABLE ACCESS (FULL) OF 'NAME_INDEX' (Cost=2519 C
ard=1516118 Bytes=36386832)
8 4 TABLE ACCESS (FULL) OF 'NAME_DOB' (Cost=540 Card=7
94119 Bytes=12705904)
9 3 TABLE ACCESS (FULL) OF 'NAME_SEX' (Cost=346 Card=177
6558 Bytes=15989022)
10 2 TABLE ACCESS (FULL) OF 'NAME_RAC' (Cost=346 Card=17765
58 Bytes=15989022)
Statistics
----------------------------------------------------------
0 recursive calls
8 db block gets
70743 consistent gets
55919 physical reads
0 redo size
3524043 bytes sent via SQL*Net to client
44093 bytes received via SQL*Net from client
3965 SQL*Net roundtrips to/from client
0 sorts (memory)
1 sorts (disk)
59451 rows processed
I rearranged the sql, and it improved a little:
SQL> SELECT c.SID, c.fbi, n.last_name || ',' || n.first_name || n.middle_name nam,
2 c.iii_status, s.sex, d.dob, r.rac, c.iffs
3 FROM (SELECT /*+ INDEX_COMBINE (cch_person bix_cch_person_status) */
4 mpi_number, sid, fbi, iii_status, iffs
5 FROM alias.cch_person
6 WHERE status_code = 'A'
7 AND enter_date_time < TO_DATE ('11/11/1951 12:00', 'MM/DD/YYYY HH24:MI')) c,
8 (SELECT mpi_number, last_name, first_name, middle_name
9 FROM alias.name_index
10 WHERE name_type_code = 'B') n,
11 (SELECT mpi_number, dob
12 FROM alias.name_dob
13 WHERE primary_value_flag = 'Y') d,
14 (SELECT mpi_number, sex
15 FROM alias.name_sex
16 WHERE primary_value_flag = 'Y') s,
17 (SELECT mpi_number, rac
18 FROM alias.name_rac
19 WHERE primary_value_flag = 'Y') r
20 WHERE c.mpi_number = n.mpi_number
21 AND n.mpi_number = d.mpi_number
22 AND d.mpi_number = s.mpi_number
23 AND s.mpi_number = r.mpi_number
24 ORDER BY c.SID;
59451 rows selected.
Elapsed: 00:02:05.06
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=59072 Card=231191 By
tes=21038381)
1 0 SORT (ORDER BY) (Cost=59072 Card=231191 Bytes=21038381)
2 1 HASH JOIN (Cost=45719 Card=231191 Bytes=21038381)
3 2 HASH JOIN (Cost=43889 Card=231191 Bytes=18957662)
4 3 HASH JOIN (Cost=42147 Card=231191 Bytes=16876943)
5 4 TABLE ACCESS (FULL) OF 'NAME_DOB' (Cost=540 Card=7
94119 Bytes=12705904)
6 4 HASH JOIN (Cost=40441 Card=348694 Bytes=19875558)
7 6 TABLE ACCESS (BY INDEX ROWID) OF 'CCH_PERSON' (C
ost=36278 Card=276807 Bytes=9134631)
8 7 BITMAP CONVERSION (TO ROWIDS)
9 8 BITMAP INDEX (SINGLE VALUE) OF 'BIX_CCH_PERS
ON_STATUS'
10 6 TABLE ACCESS (FULL) OF 'NAME_INDEX' (Cost=2519 C
ard=1516118 Bytes=36386832)
11 3 TABLE ACCESS (FULL) OF 'NAME_SEX' (Cost=346 Card=177
6558 Bytes=15989022)
12 2 TABLE ACCESS (FULL) OF 'NAME_RAC' (Cost=346 Card=17765
58 Bytes=15989022)
Statistics
----------------------------------------------------------
0 recursive calls
8 db block gets
66357 consistent gets
66703 physical reads
0 redo size
3520345 bytes sent via SQL*Net to client
44093 bytes received via SQL*Net from client
3965 SQL*Net roundtrips to/from client
0 sorts (memory)
1 sorts (disk)
59451 rows processed
Do you see somewhere I can improve more?
Thanks in advance.
Followup April 20, 2004 - 3pm Central time zone:
without tkprofs to see actuals vs guesstimates (autotrace vs reality) it is hard to say.
TKPROF
April 20, 2004 - 4pm Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
Thank you for your time. Does this tkprof helps?
SELECT c.SID, c.fbi, n.last_name || ',' || n.first_name || n.middle_name nam,
c.iii_status, s.sex, d.dob, r.rac, c.iffs
FROM (SELECT /*+ INDEX_COMBINE (cch_person bix_cch_person_status) */
mpi_number, sid, fbi, iii_status, iffs
FROM alias.cch_person
WHERE status_code = 'A'
AND enter_date_time < TO_DATE ('11/11/1951 12:00', 'MM/DD/YYYY HH24:MI')) c,
(SELECT mpi_number, last_name, first_name, middle_name
FROM alias.name_index
WHERE name_type_code = 'B') n,
(SELECT mpi_number, dob
FROM alias.name_dob
WHERE primary_value_flag = 'Y') d,
(SELECT mpi_number, sex
FROM alias.name_sex
WHERE primary_value_flag = 'Y') s,
(SELECT mpi_number, rac
FROM alias.name_rac
WHERE primary_value_flag = 'Y') r
WHERE c.mpi_number = n.mpi_number
AND n.mpi_number = d.mpi_number
AND d.mpi_number = s.mpi_number
AND s.mpi_number = r.mpi_number
ORDER BY c.SID
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.03 0.03 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 3965 58.65 227.98 65291 66358 8 59451
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3967 58.68 228.01 65291 66358 8 59451
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61
Rows Row Source Operation
------- ---------------------------------------------------
59451 SORT ORDER BY (cr=66358 r=65291 w=11546 time=226385255 us)
59451 HASH JOIN (cr=66358 r=64812 w=11067 time=225094002 us)
59451 HASH JOIN (cr=62773 r=59307 w=9135 time=192796293 us)
59451 HASH JOIN (cr=59188 r=53862 w=7259 time=162410802 us)
1516118 TABLE ACCESS FULL NAME_DOB (cr=5602 r=5488 w=0 time=23894432 us)
59451 HASH JOIN (cr=53586 r=43047 w=1932 time=104491139 us)
59452 TABLE ACCESS BY INDEX ROWID CCH_PERSON (cr=27406 r=21590 w=0 time=51747744 us)
1516094 BITMAP CONVERSION TO ROWIDS (cr=39 r=34 w=0 time=3455108 us)
72 BITMAP INDEX SINGLE VALUE BIX_CCH_PERSON_STATUS (cr=39 r=34 w=0 time=104162
us)(object id 36212)
1516117 TABLE ACCESS FULL NAME_INDEX (cr=26180 r=19525 w=0 time=36548056 us)
1776557 TABLE ACCESS FULL NAME_SEX (cr=3585 r=3569 w=0 time=13383135 us)
1776557 TABLE ACCESS FULL NAME_RAC (cr=3585 r=3573 w=0 time=16002629 us)
********************************************************************************
Followup April 21, 2004 - 12pm Central time zone:
the query itself runs pretty fast -- it is waits for IO most likely. I see lots of writing to temp
going on there.
is your hash_area_size/pga_aggregate_target set 'really small'? what are your settings for pga
workspaces?
Good questions
April 21, 2004 - 2pm Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
Thank you for still answering questions while you are at IOUG in Toronto. Your questions led me to
find out the Metalink note 223730.1 (Automatic PGA Memory Management in 9i). That was very helpful.
My workarea_size_policy was set to AUTO and pga_aggregate_target was set to 40M. In that note, it
states:
Oracle does not recommend using the HASH_AREA_SIZE parameter unless the instance is configured
with the shared server option. Oracle recommends that you enable automatic sizing of SQL working
areas by setting PGA_AGGREGATE_TARGET instead. HASH_AREA_SIZE is retained for backward
compatibility.
I have bumped up pga_aggregate_target from 40M to 100M, and the sort for the query now occurs in
the memory:
SQL> SELECT c.SID, c.fbi, n.last_name || ',' || n.first_name || n.middle_name nam,
2 c.iii_status, s.sex, d.dob, r.rac, c.iffs
3 FROM (SELECT /*+ INDEX_COMBINE (cch_person bix_cch_person_status) */
4 mpi_number, sid, fbi, iii_status, iffs
5 FROM alias.cch_person
6 WHERE status_code = 'A'
7 AND enter_date_time < TO_DATE ('11/11/1951 12:00', 'MM/DD/YYYY HH24:MI')) c,
8 (SELECT mpi_number, last_name, first_name, middle_name
9 FROM alias.name_index
10 WHERE name_type_code = 'B') n,
11 (SELECT mpi_number, dob
12 FROM alias.name_dob
13 WHERE primary_value_flag = 'Y') d,
14 (SELECT mpi_number, sex
15 FROM alias.name_sex
16 WHERE primary_value_flag = 'Y') s,
17 (SELECT mpi_number, rac
18 FROM alias.name_rac
19 WHERE primary_value_flag = 'Y') r
20 WHERE c.mpi_number = n.mpi_number
21 AND n.mpi_number = d.mpi_number
22 AND d.mpi_number = s.mpi_number
23 AND s.mpi_number = r.mpi_number
24 ORDER BY c.SID;
59450 rows selected.
Elapsed: 00:01:55.08
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=26197 Card=185404 By
tes=18169592)
1 0 SORT (ORDER BY) (Cost=26197 Card=185404 Bytes=18169592)
2 1 HASH JOIN (Cost=23326 Card=185404 Bytes=18169592)
3 2 HASH JOIN (Cost=22128 Card=185404 Bytes=16500956)
4 3 HASH JOIN (Cost=20955 Card=185404 Bytes=14832320)
5 4 HASH JOIN (Cost=19754 Card=279635 Bytes=17896640)
6 5 TABLE ACCESS (BY INDEX ROWID) OF 'CCH_PERSON' (C
ost=15614 Card=221985 Bytes=8879400)
7 6 BITMAP CONVERSION (TO ROWIDS)
8 7 BITMAP INDEX (SINGLE VALUE) OF 'BIX_CCH_PERS
ON_STATUS'
9 5 TABLE ACCESS (FULL) OF 'NAME_INDEX' (Cost=2519 C
ard=1516118 Bytes=36386832)
10 4 TABLE ACCESS (FULL) OF 'NAME_DOB' (Cost=540 Card=7
94119 Bytes=12705904)
11 3 TABLE ACCESS (FULL) OF 'NAME_SEX' (Cost=346 Card=177
6558 Bytes=15989022)
12 2 TABLE ACCESS (FULL) OF 'NAME_RAC' (Cost=346 Card=17765
58 Bytes=15989022)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
66359 consistent gets
55831 physical reads
0 redo size
3520301 bytes sent via SQL*Net to client
44092 bytes received via SQL*Net from client
3965 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
59450 rows processed
The new tkprof is:
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 3965 55.48 212.10 55348 66359 0 59450
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3967 55.51 212.13 55348 66359 0 59450
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61
********************************************************************************
OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS
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 3965 55.48 212.10 55348 66359 0 59450
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3968 55.51 212.13 55348 66359 0 59450
Misses in library cache during parse: 1
Any additional comments? Thanks again for your responses. Really appreciate your time and help.
Followup April 21, 2004 - 9pm Central time zone:
lets see the stat records from the tkprof (with the cr= and such)...
Sorry, I didn't get it
April 22, 2004 - 1pm Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
I am confused. Did you mean that I should run tkprof with option cr=....?
I didn't see tkprof provides that option. I run tkprof with option explain=...
Is that what you would like to see?
Thank you.
SELECT c.SID, c.fbi, n.last_name || ',' || n.first_name || n.middle_name nam,
c.iii_status, s.sex, d.dob, r.rac, c.iffs
FROM (SELECT /*+ INDEX_COMBINE (cch_person bix_cch_person_status) */
mpi_number, sid, fbi, iii_status, iffs
FROM alias.cch_person
WHERE status_code = 'A'
AND enter_date_time < TO_DATE ('11/11/1951 12:00', 'MM/DD/YYYY HH24:MI')) c,
(SELECT mpi_number, last_name, first_name, middle_name
FROM alias.name_index
WHERE name_type_code = 'B') n,
(SELECT mpi_number, dob
FROM alias.name_dob
WHERE primary_value_flag = 'Y') d,
(SELECT mpi_number, sex
FROM alias.name_sex
WHERE primary_value_flag = 'Y') s,
(SELECT mpi_number, rac
FROM alias.name_rac
WHERE primary_value_flag = 'Y') r
WHERE c.mpi_number = n.mpi_number
AND n.mpi_number = d.mpi_number
AND d.mpi_number = s.mpi_number
AND s.mpi_number = r.mpi_number
ORDER BY c.SID
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 3965 55.59 177.47 66826 67207 0 59450
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3967 55.62 177.50 66826 67207 0 59450
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 61 (ALIAS)
Rows Row Source Operation
------- ---------------------------------------------------
59450 SORT ORDER BY (cr=67207 r=66826 w=2730 time=175966470 us)
59450 HASH JOIN (cr=67207 r=66826 w=2730 time=173988156 us)
59450 HASH JOIN (cr=63622 r=62665 w=1710 time=146075237 us)
59450 HASH JOIN (cr=60037 r=58594 w=780 time=119320772 us)
59450 HASH JOIN (cr=54435 r=52785 w=135 time=92194054 us)
59451 TABLE ACCESS BY INDEX ROWID CCH_PERSON (cr=27407 r=27377 w=0 time=45900691 us)
1516093 BITMAP CONVERSION TO ROWIDS (cr=40 r=40 w=0 time=4613420 us)
72 BITMAP INDEX SINGLE VALUE BIX_CCH_PERSON_STATUS (cr=40 r=40 w=0 time=140011
us)(object id 36212)
1516116 TABLE ACCESS FULL NAME_INDEX (cr=27028 r=25273 w=0 time=34664686 us)
1516117 TABLE ACCESS FULL NAME_DOB (cr=5602 r=5164 w=0 time=8303261 us)
1776556 TABLE ACCESS FULL NAME_SEX (cr=3585 r=3141 w=0 time=11572124 us)
1776556 TABLE ACCESS FULL NAME_RAC (cr=3585 r=3141 w=0 time=10345249 us)
Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: CHOOSE
59450 SORT (ORDER BY)
59450 HASH JOIN
59450 HASH JOIN
59450 HASH JOIN
59450 HASH JOIN
59451 TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF
'CCH_PERSON'
1516093 BITMAP CONVERSION (TO ROWIDS)
72 BITMAP INDEX (SINGLE VALUE) OF
'BIX_CCH_PERSON_STATUS'
1516116 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'NAME_INDEX'
1516117 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'NAME_DOB'
1776556 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'NAME_SEX'
1776556 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'NAME_RAC'
********************************************************************************
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: 0
Optimizer goal: CHOOSE
Parsing user id: 61 (ALIAS)
********************************************************************************
OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 3 0.03 0.02 0 0 0 0
Execute 5 0.00 0.00 0 0 0 0
Fetch 3965 55.59 177.47 66826 67207 0 59450
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3973 55.62 177.50 66826 67207 0 59450
Misses in library cache during parse: 2
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.
1 statement EXPLAINed in this session.
********************************************************************************
Trace file: aliasdev_ora_4072.trc
Trace file compatibility: 9.00.01
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.
4 unique SQL statements in trace file.
1 SQL statements EXPLAINed using schema:
ALIAS.prof$plan_table
Default table was used.
Table was created.
Table was dropped.
4051 lines in trace file.
Followup April 23, 2004 - 8am Central time zone:
no, tkprof provides the cr= statistics:
59450 SORT ORDER BY (cr=67207 r=66826 w=2730 time=175966470 us)
^^^^^^^^ consistent reads
^^^^^^ shows temp is still used
^^^^^^^ shows lots of physical IO
as compared to the original:
59451 SORT ORDER BY (cr=66358 r=65291 w=11546 time=226385255 us)
it is better for temp usage and some 50 seconds faster, but the physical IO is the last barrier.
You have lots of stuff like this:
(SELECT mpi_number, rac
FROM alias.name_rac
WHERE primary_value_flag = 'Y') r
and that is going to be hard to get around (looks like a "data model issue" in some regards. I
would prefer to keep "primary data" as attributes of the parent record itself and normalize out
historical or secondary values in the detail table like this -- eg: primary_value_flag would not
exist, DOB would be an attribute of cch_person directly -- in fact, does anyone really have a "non
primary DOB"?)
Anyway, try this:
select c.sid, c.fbi,
(select n.last_name || ',' || n.first_name || n.middle_name nam
from alias.name_index
where name_type_code = 'B'
and mpi_number = c.mpi_number ) name,
c.iii_status,
(select sex
from alias.name_sex
where primary_value_flag='Y'
and mpi_number = c.mpi_number ) sex,
(select dob
from alias.name_dob
where primary_value_flag = 'Y'
and mpi_number = c.mpi_number ) dob,
(select rac
from alias.name_rac
where primary_value_flag = 'Y'
and mpi_number = c.mpi_number ) rac,
c.iffs
from alias.cch_person
where status_code = 'A'
and enter_date_time < TO_DATE ('11/11/1951 12:00', 'MM/DD/YYYY HH24:MI')
assuming indexes of the form
name_rac(mpi_number,primary_value_flag,name_rac)
on each of the detail tables (and assuming the mpi_number,primary_value is in fact the PRIMARY KEY
of the detail table), you *might* find that to be better (it'll get the first rows faster for sure)
sql tuning
April 28, 2004 - 4am Central time zone
Reviewer: k.Venkat from India
Tom,
We have a query against a view. The tkprof report of the query and also the
query related to the view generation is given below: The LIOs are high. How we
can decrease the LIOs and improve the performance time? Give your guidance.
TKPROF report on the query against the view:
SELECT APPLICATIONID,
ID,
FIRSTNAME,
LASTNAME,
EMAIL,
PHONE,
CREATEDON,
Taskid,
Queueid,
Workitemid,
Itemtypeid
FROM loscd.OCR_APP_LIST_VIEW_NEW
WHERE TaskId = 301 AND QueueId = 145
ORDER BY APPLICATIONID
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 268 0.46 0.48 0 20419 0 4000
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 270 0.50 0.54 0 20419 0 4000
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64
Rows Row Source Operation
------- ---------------------------------------------------
4000 SORT ORDER BY
4000 NESTED LOOPS
4000 NESTED LOOPS
4000 HASH JOIN
4000 HASH JOIN
4000 TABLE ACCESS BY INDEX ROWID OW_WORKITEM_INSTANCE
4000 INDEX RANGE SCAN IDX_NU_TID_QID (object id 19609)
52565 TABLE ACCESS FULL OW_WORKITEM
11042 TABLE ACCESS FULL OCR_CREDIT_WORKFLOW_MAP
4000 TABLE ACCESS BY INDEX ROWID OCR_PTA_WB
4000 INDEX RANGE SCAN IDX_OCR_PTA_WB (object id 23181)
4000 TABLE ACCESS BY INDEX ROWID OCR_APPL_WB
4000 INDEX UNIQUE SCAN CLOS_APPL_WB_PK (object id 23117)
************************************************************
TKPROF report of the query on the view generation:
SELECT A.APP_ID
APPLICATIONID,
A.FIRST_NAME FIRSTNAME,
A.LAST_NAME LASTNAME,
B.WORKITEMID ID,
A.EMAIL EMAIL,
A.MOBILE_PHONE PHONE,
TO_CHAR(E.MAKER_DATE, 'DD-MON-YYYY') CREATEDON,
C.WORKITEMID WORKITEMID,
B.TASKID TASKID,
B.QUEUEID QUEUEID,
C.ITEMTYPEID ITEMTYPEID,
B.STATUS STATUS
FROM OCR_PTA_WB A,
OW_WORKITEM_INSTANCE B,
OW_WORKITEM C,
OCR_CREDIT_WORKFLOW_MAP D,
OCR_APPL_WB E
WHERE B.WORKITEMID = C.WORKITEMID AND
A.APP_ID = D.ID_NO AND
A.APP_ID = E.APP_ID AND
B.WORKITEMID= C.WORKITEMID AND
C.WORKITEMID = D.WORK_ITEM_ID AND
D.ID_TYPE = 'A' AND A.ROLE_CODE='A'
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 737 1.48 4.06 242 58642 0 11033
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 739 1.50 4.08 242 58642 0 11033
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 64
Rows Row Source Operation
------- ---------------------------------------------------
11033 NESTED LOOPS
11033 NESTED LOOPS
11041 HASH JOIN
11042 HASH JOIN
11042 TABLE ACCESS FULL OCR_CREDIT_WORKFLOW_MAP
52565 TABLE ACCESS FULL OW_WORKITEM
23045 TABLE ACCESS FULL OW_WORKITEM_INSTANCE
11033 TABLE ACCESS BY INDEX ROWID OCR_PTA_WB
11033 INDEX RANGE SCAN IDX_OCR_PTA_WB (object id 23181)
11033 TABLE ACCESS BY INDEX ROWID OCR_APPL_WB
11033 INDEX UNIQUE SCAN CLOS_APPL_WB_PK (object id 23117)
************************************************************
Thanks,
Venkat
Constant in the join ?
May 6, 2004 - 10am Central time zone
Reviewer: Hector Gabriel Ulloa Ligarius from Providencia , Santiago of Chile
Hi Tom ...
How are you?
I have seen a consultation that has in its body the following thing:
select <fields_a,fields_b>
from <table_a,table_b>
where <table_a.fields_a> = <table_b.fields_b>
and var_dato1 = <number>
;
In the declare section
declare
var_Dato1 number;
.
.
.
The question...
Is better , extract the line
and var_dato1 = <number> and to place it in if
if var_dato1 = <number> then
select <fields_a,fields_b>
from <table_a,table_b>
where <table_a.fields_a> = <table_b.fields_b>
In the explain :
SQL> create table ulloa as select * from user_objects;
Table created.
SQL> select count(*) from ulloa;
COUNT(*)
---------
3523
SQL> create table ulloa2 as select * from user_objects where rownum < 1001;
Table created.
SQL> select count(*) from ulloa2;
COUNT(*)
---------
1000
SQL> set autotrace traceonly exp
SQL> select a.* , b.* from ulloa a , ulloa2 b where a.object_name = b.object_name;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 MERGE JOIN
2 1 SORT (JOIN)
3 2 TABLE ACCESS (FULL) OF 'ULLOA2'
4 1 SORT (JOIN)
5 4 TABLE ACCESS (FULL) OF 'ULLOA'
SQL> var var_dato1 number;
SQL> select a.* , b.* from ulloa a , ulloa2 b where a.object_name = b.object_name and :var_dato1 =
0
SQL> /
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 FILTER
2 1 MERGE JOIN
3 2 SORT (JOIN)
4 3 TABLE ACCESS (FULL) OF 'ULLOA2'
5 2 SORT (JOIN)
6 5 TABLE ACCESS (FULL) OF 'ULLOA'
SQL>
The step FILTER is very EXPENSIVE??
More expensive in the select or in the if?
Some suggestions?
Thank you again Tom Kyte..
Regards
Hector Ulloa Ligarius
Followup May 6, 2004 - 1pm Central time zone:
if I had this
select <fields_a,fields_b>
from <table_a,table_b>
where <table_a.fields_a> = <table_b.fields_b>
and var_dato1 = <number>
;
and i was writing code, i would definitely code the if (var_dato1 = number) instead of doing it in
the query like that.
the best way to speed up something -- is to not do it.
Constant in the Join?
May 7, 2004 - 8am Central time zone
Reviewer: Hector Gabriel Ulloa Ligarius from Santiago of Chile
Muchas gracias Tom
Regards
Hector Ulloa Ligarius
q on query!
May 26, 2004 - 9pm Central time zone
Reviewer: A reader
Hi Tom
Quick question:
Consider the following schema and the final query
(at the end):
-------
scott@ORA10G> drop table c1;
Table dropped.
scott@ORA10G> drop table c2;
Table dropped.
scott@ORA10G> drop table p1;
Table dropped.
scott@ORA10G>
scott@ORA10G> create table p1
2 (
3 x varchar2(10) primary key
4 );
Table created.
scott@ORA10G>
scott@ORA10G> create table c1
2 (
3 x varchar2(10) references p1,
4 y int
5 );
Table created.
scott@ORA10G>
scott@ORA10G> create table c2
2 (
3 x varchar2(10) references p1,
4 y int
5 );
Table created.
scott@ORA10G>
scott@ORA10G> insert into p1 values( 'x1');
1 row created.
scott@ORA10G> insert into p1 values( 'x2');
1 row created.
scott@ORA10G>
scott@ORA10G> insert into c1 values( 'x1', 1);
1 row created.
scott@ORA10G> insert into c1 values( 'x1', 2);
1 row created.
scott@ORA10G> insert into c1 values( 'x2', 2);
1 row created.
scott@ORA10G>
scott@ORA10G> insert into c2 values( 'x1', 5);
1 row created.
scott@ORA10G> insert into c2 values( 'x1', 6);
1 row created.
scott@ORA10G> insert into c2 values( 'x2', 7);
1 row created.
scott@ORA10G>
scott@ORA10G> prompt p1 data
p1 data
scott@ORA10G> select * from p1;
X
----------
x1
x2
scott@ORA10G> prompt c1 data
c1 data
scott@ORA10G> select * from c1;
X Y
---------- ----------
x1 1
x1 2
x2 2
scott@ORA10G> prompt c2 data
c2 data
scott@ORA10G> select * from c2;
X Y
---------- ----------
x1 5
x1 6
x2 7
scott@ORA10G>
scott@ORA10G> prompt final query result
final query result
scott@ORA10G> select c1_y_sum, c2_y_sum
2 from
3 (
4 select c1.x c1x, sum(c1.y) c1_y_sum
5 from p1, c1
6 where p1.x = c1.x
7 group by c1.x
8 ),
9 (
10 select c2.x c2_x, sum(c2.y) c2_y_sum
11 from p1, c2
12 where p1.x = c2.x
13 group by c2.x
14 ) ;
C1_Y_SUM C2_Y_SUM
---------- ----------
3 11
3 7
2 11
2 7
---------------
Is there any other way to get the sums that I get in
the final query. In this case we have only 2 table joins
in each sub select - but consider the case
when we are joining with multiple tables as:
select c1.x c1x, sum(c1.y) c1_y_sum
from p1, p2, c1
in the first sub select and
select c2.x c2_x, sum(c2.y) c2_y_sum
from p1, p2, c2
in the second sub select. Would you have
written it in the same way or can we do some
kind of "refactoring" to optimize the query. (Since
joining to p1, p2 etc is being repeated.)
I hope I am making sense!
My hunch is that optimizer takes care of it but
I wanted to get your opinion.
Thanx!
Followup May 27, 2004 - 8am Central time zone:
well, i would say in this case the join to P1 is not even necessary or desired. A simple "where x
is not null" is more than sufficient since if x is not null, we know x must be in p1 and since x is
unique in p1, it is in there at most once. so a simple
scott@ORA10G> select c1_y_sum, c2_y_sum
2 from
3 (
4 select c1.x c1x, sum(c1.y) c1_y_sum
5 from c1
6 where c1.x is not null
8 ),
9 (
10 select c2.x c2_x, sum(c2.y) c2_y_sum
11 from c2
12 where c2.x is not null
14 ) ;
would do. Now, in the more general case
p1,p2,c1
p1,p2,c2
what if c1 is small -- the join might be:
c1 nested loops p2 nested loops p1
resulting in 5 rows from c1, getting 20 rows from p2 getting 20 rows from p1
and c2 is big -- the join might be:
p1 hash join p2 hash join c2
every row in p1 with every row in p2
Now, if we tried to "reuse" p1 X p2, we'd have to pick the "bigger" one, that would not be good for
the first query.
or consider both c1 and c2 are "small" c1 has numbers 1..100, c2 has numbers 500..600.
c1 will get an entirely DIFFERENT set of rows from p1Xp2 than c2 will get. It would be horribly
inefficient to generate p1Xp2 here and then have c1/c2 join to it. we would use c1/c2 to *drive*
into p2/p1 and pick up just the rows we need for each.
don't attribute procedural logic to SQL (sets, think in sets). The p1Xp2 isn't constant for each
of the subqueries.
thanx Tom!
May 27, 2004 - 12pm Central time zone
Reviewer: A reader
"well, i would say in this case the join to P1 is not even necessary or desired.
A simple "where x is not null" is more than sufficient since if x is not null,
we know x must be in p1 and since x is unique in p1, it is in there at most
once."
Aha - I guess I would not have thought of that!
However, in my actual case this is not true - we just
have bunch of tables with foreign key linkage.
The two "sub queries" have some commonality of tables with
only one table being different.
1. Does that change your answer in any way - would you want
to add some more thoughts?
"what if c1 is small -- the join might be:
c1 nested loops p2 nested loops p1
resulting in 5 rows from c1, getting 20 rows from p2 getting 20 rows from p1
and c2 is big -- the join might be:
p1 hash join p2 hash join c2
every row in p1 with every row in p2
Now, if we tried to "reuse" p1 X p2, we'd have to pick the "bigger" one, that
would not be good for the first query.
"
2. I think what you are saying is that it would be optimal
to separately join (like I did) in this case because
CBO gets a chance to optimize each separately
and then combine the results - correct?
3. I did not quite understand what you mean by "you would have to pick the bigger one"? what is the
"bigger one" and
why do you have to pick it?:)
4. This is unrelated to the above question. Just
curious how much time you spend daily answering
questions on asktom?
Thanx Tom!!
Followup May 27, 2004 - 8pm Central time zone:
1) i did -- p1 x p2 isn't a "constant" here -- the choice of c1 or c2 defintely and materially
affect the processing.
2) dead on
3) in the case i was using -- we had a "small p1xp2" and a "big p1xp2", the small one would not
work when we needed big so big would have to be used for both -- suboptimal
4) too much is what I hear.
thanx!
May 27, 2004 - 9pm Central time zone
Reviewer: A reader
"too much is what I hear"
not sure what to make of that - anyways
thank you so much - I learn at least one
new thing every day from your site!
Need your help on this sql tuning
June 1, 2004 - 11am Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
I have tables cch_person and incident, which are one to many related by pid_number. One person
could have many incidents. I need to retrieve all person records where only one active incident is
associated withe the person. Here is what I wrote:
SELECT i1.pid_number, i1.inc_number, c.SID, c.fbi, i1.ctn, i1.tcn, i1.oca
FROM alias.cch_person c, alias.incident i1
WHERE (c.iii_status != 'MSO' OR c.iii_status IS NULL)
AND c.prn IS NULL
AND c.status_code = 'A'
AND i1.status_code = 'A'
AND i1.pid_number = c.pid_number
AND i1.pid_number IN (SELECT /*+ CARDINALITY(I2 895637 ) */
i2.pid_number
FROM alias.incident i2
WHERE i2.pid_number = i1.pid_number
GROUP BY i2.pid_number
HAVING COUNT (i2.pid_number) = 1);
1. Do you have more efficient way for this query?
2. Should I use /*+ CARDINALITY(I2 895637 ) */ hint there? I saw the consistent gets were reduced
from the explain plan, but not sure how it works after table grows.
Thanks so much for your help.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=150276 Card=47125 By
tes=2921750)
1 0 FILTER
2 1 HASH JOIN (Cost=8901 Card=47125 Bytes=2921750)
3 2 TABLE ACCESS (FULL) OF 'INCIDENT' (Cost=5560 Card=4712
5 Bytes=1555125)
4 2 TABLE ACCESS (FULL) OF 'CCH_PERSON' (Cost=3107 Card=32
2486 Bytes=9352094)
5 1 FILTER
6 5 SORT (GROUP BY NOSORT) (Cost=3 Card=1 Bytes=6)
7 6 INDEX (RANGE SCAN) OF 'PK_INCIDENT' (UNIQUE) (Cost=3
Card=895637 Bytes=5373822)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
3565186 consistent gets
102387 physical reads
0 redo size
38217881 bytes sent via SQL*Net to client
500064 bytes received via SQL*Net from client
45417 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
681228 rows processed
Followup June 1, 2004 - 3pm Central time zone:
lose the hint (statistics would be supplying that already) but give analytics a try:
select *
from (
SELECT i1.pid_number, i1.inc_number, c.SID, c.fbi, i1.ctn, i1.tcn, i1.oca,
count(*) over (partition by i1.pid_number) cnt
FROM alias.cch_person c, alias.incident i1
WHERE (c.iii_status != 'MSO' OR c.iii_status IS NULL)
AND c.prn IS NULL
AND c.status_code = 'A'
AND i1.status_code = 'A'
AND i1.pid_number = c.pid_number
)
where cnt = 1
or
SELECT i1.pid_number, i1.inc_number, c.SID, c.fbi, i1.ctn, i1.tcn, i1.oca,
from ( select *
from ( SELECT i1.pid_number, i1.inc_number, i1.ctn, i1.tcn, i1.oca,
count(*) over (partition by i1.pid_number) cnt
FROM alias.incident i1
WHERE i1.status_code = 'A'
)
where cnt = 1 ) il,
alias.cch_person c
WHERE (c.iii_status != 'MSO' OR c.iii_status IS NULL)
AND c.prn IS NULL
AND c.status_code = 'A'
AND i1.pid_number = c.pid_number
Thank You!!!
June 1, 2004 - 4pm Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
Many, many thanks! Your sql reduced consistent gets dramatically (See below), but I haven't figured
out why the number of rows processed is different. I am not familiar with analytics. Did you have
more examples somewhere?
Thanks again for your help.
SQL> select *
2 from (
3 SELECT i1.pid_number, i1.inc_number, c.SID, c.fbi, i1.ctn, i1.tcn, i1.oca,
4 count(*) over (partition by i1.pid_number) cnt
5 FROM alias.cch_person c, alias.incident i1
6 WHERE (c.iii_status != 'MSO' OR c.iii_status IS NULL)
7 AND c.prn IS NULL
8 AND c.status_code = 'A'
9 AND i1.status_code = 'A'
10 AND i1.pid_number = c.pid_number
11 )
12 where cnt = 1
13 ;
681234 rows selected.
Elapsed: 00:03:45.09
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=14900 Card=384006 By
tes=31104486)
1 0 VIEW (Cost=14900 Card=384006 Bytes=31104486)
2 1 WINDOW (SORT) (Cost=14900 Card=384006 Bytes=23808372)
3 2 HASH JOIN (Cost=10919 Card=384006 Bytes=23808372)
4 3 TABLE ACCESS (FULL) OF 'CCH_PERSON' (Cost=3107 Card=
322486 Bytes=9352094)
5 3 TABLE ACCESS (FULL) OF 'INCIDENT' (Cost=5560 Card=94
2506 Bytes=31102698)
Statistics
----------------------------------------------------------
0 recursive calls
52 db block gets
90107 consistent gets
155635 physical reads
0 redo size
35966755 bytes sent via SQL*Net to client
500064 bytes received via SQL*Net from client
45417 SQL*Net roundtrips to/from client
0 sorts (memory)
1 sorts (disk)
681234 rows processed
SQL> SELECT i1.pid_number, i1.inc_number, c.SID, c.fbi, i1.ctn, i1.tcn, i1.oca
2 FROM alias.cch_person c, alias.incident i1
3 WHERE (c.iii_status != 'MSO' OR c.iii_status IS NULL)
4 AND c.prn IS NULL
5 AND c.status_code = 'A'
6 AND i1.status_code = 'A'
7 AND i1.pid_number = c.pid_number
8 AND i1.pid_number IN (SELECT /*+ CARDINALITY(I2 895637 ) */
9 i2.pid_number
10 FROM alias.incident i2
11 WHERE i2.pid_number = i1.pid_number
12 GROUP BY i2.pid_number
13 HAVING COUNT (i2.pid_number) = 1);
681228 rows selected.
Elapsed: 00:03:01.05
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=150276 Card=47125 By
tes=2921750)
1 0 FILTER
2 1 HASH JOIN (Cost=8901 Card=47125 Bytes=2921750)
3 2 TABLE ACCESS (FULL) OF 'INCIDENT' (Cost=5560 Card=4712
5 Bytes=1555125)
4 2 TABLE ACCESS (FULL) OF 'CCH_PERSON' (Cost=3107 Card=32
2486 Bytes=9352094)
5 1 FILTER
6 5 SORT (GROUP BY NOSORT) (Cost=3 Card=1 Bytes=6)
7 6 INDEX (RANGE SCAN) OF 'PK_INCIDENT' (UNIQUE) (Cost=3
Card=895637 Bytes=5373822)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
3568464 consistent gets
129619 physical reads
0 redo size
38211744 bytes sent via SQL*Net to client
500064 bytes received via SQL*Net from client
45417 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
681228 rows processed
Followup June 1, 2004 - 5pm Central time zone:
ahh, you filter out people who have an ACTIVE and <any other status record>
8 AND i1.pid_number IN (SELECT /*+ CARDINALITY(I2 895637 ) */
9 i2.pid_number
10 FROM alias.incident i2
11 WHERE i2.pid_number = i1.pid_number
12 GROUP BY i2.pid_number
13 HAVING COUNT (i2.pid_number) = 1);
You didn't have a predicate on status_code in there. I took:
...
I need to retrieve all person
records where only one active incident is associated withe the person.
......
literally.
If you meant "only one ACTIVE incident", then my query is right and your query is missing them.
If you meant "only one incident (regardless of status)", then my query is 'wrong' (but easy to fix)
and your query is right.
SQL> select *
2 from (
3 SELECT i1.pid_number, i1.inc_number, c.SID, c.fbi, i1.ctn, i1.tcn, i1.oca,
4 count(*) over (partition by i1.pid_number) cnt, i1.status_code
5 FROM alias.cch_person c, alias.incident i1
6 WHERE (c.iii_status != 'MSO' OR c.iii_status IS NULL)
7 AND c.prn IS NULL
8 AND c.status_code = 'A'
10 AND i1.pid_number = c.pid_number
11 )
12 where cnt = 1 and status_code = 'A'
13 ;
just need to move the predicate on status_code to after the analytics.
Tons of examples on this site (search for:
analytics rock roll
) or I have a chapter on them in Expert one on one Oracle if you have access to that book.
T H A N K Y O U
June 1, 2004 - 8pm Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
That explains. Your code works well either way. Thank you so, so very much. I will find time to
read more about analytics...
Again, thank you for teaching us.
Followup June 2, 2004 - 7am Central time zone:
Oh, also try:
SELECT i1.pid_number, max(i1.inc_number), max(c.SID),
max(c.fbi), max(i1.ctn), max(i1.tcn), max(i1.oca)
FROM alias.cch_person c, alias.incident i1
WHERE (c.iii_status != 'MSO' OR c.iii_status IS NULL)
AND c.prn IS NULL
AND c.status_code = 'A'
AND i1.status_code = 'A'
AND i1.pid_number = c.pid_number
GROUP BY i1.pid_number
having count(*) = 1
or
SELECT i1.pid_number, max(i1.inc_number), max(c.SID),
max(c.fbi), max(i1.ctn), max(i1.tcn), max(i1.oca)
FROM alias.cch_person c, alias.incident i1
WHERE (c.iii_status != 'MSO' OR c.iii_status IS NULL)
AND c.prn IS NULL
AND c.status_code = 'A'
AND i1.pid_number = c.pid_number
GROUP BY i1.pid_number
having count(*) = 1 and max(status_code) = 'A'
It is even faster
June 2, 2004 - 4pm Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
It is indeed faster compared to using the analytics way. They both speed up my stored procedure
70%:
SQL> SELECT *
2 FROM (SELECT i.pid_number, i.inc_number, c.SID, c.fbi,
3 i.ctn, i.tcn, i.oca,
4 COUNT (*) OVER (PARTITION BY i.pid_number) cnt,
5 i.status_code
6 FROM alias.cch_person c, alias.incident i
7 WHERE (c.iii_status != 'MSO' OR c.iii_status IS NULL
8 )
9 AND c.prn IS NULL
10 AND c.status_code = 'A'
11 AND i.status_code = 'A'
12 AND i.pid_number = c.pid_number)
13 WHERE cnt = 1;
681235 rows selected.
Elapsed: 00:04:37.02
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=14900 Card=384006 By
tes=32256504)
1 0 VIEW (Cost=14900 Card=384006 Bytes=32256504)
2 1 WINDOW (SORT) (Cost=14900 Card=384006 Bytes=23808372)
3 2 HASH JOIN (Cost=10919 Card=384006 Bytes=23808372)
4 3 TABLE ACCESS (FULL) OF 'CCH_PERSON' (Cost=3107 Card=
322486 Bytes=9352094)
5 3 TABLE ACCESS (FULL) OF 'INCIDENT' (Cost=5560 Card=94
2506 Bytes=31102698)
Statistics
----------------------------------------------------------
0 recursive calls
59 db block gets
90107 consistent gets
152491 physical reads
0 redo size
36648088 bytes sent via SQL*Net to client
500064 bytes received via SQL*Net from client
45417 SQL*Net roundtrips to/from client
0 sorts (memory)
1 sorts (disk)
681235 rows processed
SQL> SELECT i.pid_number, max(i.inc_number), max(c.SID),
2 max(c.fbi), max(i.ctn), max(i.tcn), max(i.oca)
3 FROM alias.cch_person c, alias.incident i
4 WHERE (c.iii_status != 'MSO' OR c.iii_status IS NULL)
5 AND c.prn IS NULL
6 AND c.status_code = 'A'
7 AND i.status_code = 'A'
8 AND i.pid_number = c.pid_number
9 GROUP BY i.pid_number
10 HAVING COUNT(*) = 1;
681235 rows selected.
Elapsed: 00:03:15.01
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=13062 Card=3841 Byte
s=238142)
1 0 FILTER
2 1 SORT (GROUP BY) (Cost=13062 Card=3841 Bytes=238142)
3 2 HASH JOIN (Cost=10919 Card=384006 Bytes=23808372)
4 3 TABLE ACCESS (FULL) OF 'CCH_PERSON' (Cost=3107 Card=
322486 Bytes=9352094)
5 3 TABLE ACCESS (FULL) OF 'INCIDENT' (Cost=5560 Card=94
2506 Bytes=31102698)
Statistics
----------------------------------------------------------
0 recursive calls
44 db block gets
90107 consistent gets
119023 physical reads
0 redo size
35966781 bytes sent via SQL*Net to client
500064 bytes received via SQL*Net from client
45417 SQL*Net roundtrips to/from client
0 sorts (memory)
1 sorts (disk)
681235 rows processed
Thank you for your dedication and excellence!
Query with ROWNUM
June 7, 2004 - 4pm Central time zone
Reviewer: Tony from Canada
Hi Tom,
Thanks a lot for your precious comments,
I have a query which is taking about 2 minutes on a one million rows table. The query is straight
forward I mean no joins etc.
select * from ( select -----.....----- from PART_t L where 1 = 1 AND L.d_code = 'XYZ' AND
L.carrier_id =
'ABC' AND L.PART_TYP = '2' AND L.DIS_STA = 5230 order by L.PART_ID desc ) where
rownum <= 15;
The explain plan shows that it's using the index(on d_code,carrier_id,part_typ columns) on the
part_t.
If I use first_rows hint then this query comes backs in fraction of a sec but the cost is very high
with hint and it uses the primary key index (on PART_ID ).
Here are the results :
WITH first_rows Hint:
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=33797 Card
=1409 Bytes=1875379)
1 0 COUNT (STOPKEY)
2 1 VIEW (Cost=33797 Card=1409 Bytes=1875379)
3 2 TABLE ACCESS (BY INDEX ROWID) OF PART_T' (Cost=3379
7 Card=1409 Bytes=481878)
4 3 INDEX (FULL SCAN DESCENDING) OF 'PK_PART' (UNIQUE)
(Cost=2490 Card=1409)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
35 consistent gets
21 physical reads
0 redo size
14049 bytes sent via SQL*Net to client
1713 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
18 rows processed
WITHOUT hint:
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=178 Card=1409 Bytes
1875379)
1 0 COUNT (STOPKEY)
2 1 VIEW (Cost=178 Card=1409 Bytes=1875379)
3 2 SORT (ORDER BY STOPKEY) (Cost=178 Card=1409 Bytes=481
78)
4 3 TABLE ACCESS (BY INDEX ROWID) OF 'PART_T' (Cost=1
6 Card=1409 Bytes=481878)
5 4 INDEX (RANGE SCAN) OF 'PART_I7' (NON-UNIQUE) (C
st=12 Card=1409)
Statistics
----------------------------------------------------------
0 recursive calls
6 db block gets
55744 consistent gets
55748 physical reads
0 redo size
14049 bytes sent via SQL*Net to client
1695 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
2 sorts (memory)
1 sorts (disk)
18 rows processed
Now if I change the query little bit i.e. by taking rownum from outer query and putting it in the
inner query, the results are very fast again:
select * from ( select -----.....----- from PART_t L where 1 = 1 AND L.d_code = 'XYZ' AND
L.carrier_id =
'ABC' AND L.PART_TYP = '2' AND L.DIS_STA = 5230
AND ROWNUM <= 15order by L.PART_ID desc ) ;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=178 Card=1409 Bytes=
1875379)
1 0 VIEW (Cost=178 Card=1409 Bytes=1875379)
2 1 SORT (ORDER BY) (Cost=178 Card=1409 Bytes=481878)
3 2 COUNT (STOPKEY)
4 3 TABLE ACCESS (BY INDEX ROWID) OF 'PART_T' (Cost=10
6 Card=1409 Bytes=481878)
5 4 INDEX (RANGE SCAN) OF 'PART_I7' (NON-UNIQUE) (Co
st=12 Card=1409)
Statistics
----------------------------------------------------------
3878 recursive calls
0 db block gets
971 consistent gets
12 physical reads
0 redo size
10573 bytes sent via SQL*Net to client
1694 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
7 sorts (memory)
0 sorts (disk)
18 rows processed
the Execution Plan of this query is the same as original query itself but it's very fast and the
reason is in "consistent gets" I guess.
Please tell me why there is huge diff. in consistent gets when the Execution Plan of both is same.
I can not use hints(application restrictions) is it right thing to use the rownum inside the nested
query , I get the same results in my test env. but somebody told me that the logic is compleltely
different and I might end up getting wrong data.
Followup June 7, 2004 - 4pm Central time zone:
select * from t where rownum <= 15 order by x
does this:
a) gets 15 random rows from t
b) sorts them
where as
select * from ( select * from t order by x ) where rownum <= 15
a) orders the result set by x
b) gets the first 15 after sorting.
so, you cannot compare them.
Why cannot you use a safe, positive, good hint? you *want* first rows, not the standard all rows
optimization here.
Best Optimisation
June 8, 2004 - 3am Central time zone
Reviewer: Saar from UAE
If a table has more than 10,000 records and I want to flush the table every time when I start it.
what do u suggest to use if I don?
Records getting stored in this way..
id serialnumber from to
20 1 ---- ----
20 2 ---- ----
20 3 ---- ----
20 4 ---- ----
21 1 ---- ----
21 2 ---- ----
21 3 ---- ----
22 1 ---- ----
22 2 ---- ----
22 3 ---- ----
22 4 ---- ----
Best practise to delete this table....
1. Delete <table_name>;
2. Take bulk collect of unique id using select statements and use Forall statement to delete it.
Pls. advice
Followup June 8, 2004 - 8am Central time zone:
3) use a global temporary table, it sounds like you want a temp table
but -- #2 would be the worst way to do it. #1 would be OK if you wanted to be able to rollback.
#4 would be "truncate"
MERGE JOIN CARTESIAN
June 22, 2004 - 3am Central time zone
Reviewer: Goh Seong Hin from Malaysia
Dear Tom,
Would need your advice on this as I found one of the slow SQL which contain a very high value in
MERGE JOIN CARTESIAN. The following is the SQL statement, tkprof and Exp Plan for the Sql.
** From your advice in others thread, the outer join can be take out rite. ( pl.item_id =
msi.inventory_item_id (+) )
** Your advice not to use function in predicate.How can I restructe this predicate.
nvl(NULL,pov.vendor_name ) = xxx
** hr_employees, po_headers, po_lines is VIEW
** mtl_system_items is SYNONYM
** How can i determine which tables is causing MERGE JOIN CARTESIAN and return such a huge rows.
Can I say that exp no. 7 (FTS of 'PER_ALL_PEOPLE_F') and 9 (FTS of 'PO_VENDORS') causing the
Cartesian JOIN and I should look at the link for this two tables.
** Appreciate if you can advice us in this SQL tuning.
select distinct pov.vendor_name,
MSI.SEGMENT1 C_FLEX_ITEM , msi.segment1 item_name,
hre . employee_id
FROM hr_employees hre , po_vendors pov , po_headers poh ,
po_lines pl , mtl_system_items msi
WHERE poh . agent_id = hre . employee_id
AND poh . vendor_id = pov . vendor_id
and pl.item_id = msi.inventory_item_id (+)
AND msi.organization_id = 1
and pl.po_header_id = poh . po_header_id
AND poh.type_lookup_code in ('STANDARD','BLANKET','PLANNED')
AND nvl(poh.cancel_flag,'N') = 'N'
AND pov.vendor_name BETWEEN nvl(NULL , pov.vendor_name )
AND nvl(NULL,pov.vendor_name )
AND hre.full_name = nvl (NULL , hre.full_name )
order by pov.vendor_name
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.20 0.20 0 0 0 0
Execute 2 0.00 0.00 0 0 0 0
Fetch 1 40.56 11.95 4 1040 12 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 40.76 120.15 4 1040 12 0
Rows Row Source Operation
------- ---------------------------------------------------
0 SORT UNIQUE
0 FILTER
1 NESTED LOOPS
1 NESTED LOOPS
1 HASH JOIN
3670156 MERGE JOIN CARTESIAN
1149 TABLE ACCESS FULL PER_ALL_PEOPLE_F
3670156 SORT JOIN
3197 TABLE ACCESS FULL PO_VENDORS
0 TABLE ACCESS FULL PO_HEADERS_ALL
0 TABLE ACCESS BY INDEX ROWID PO_LINES_ALL
0 INDEX RANGE SCAN (object id 45108)
0 TABLE ACCESS BY INDEX ROWID MTL_SYSTEM_ITEMS_B
0 INDEX UNIQUE SCAN (object id 38017)
0 SORT AGGREGATE
0 TABLE ACCESS FULL FINANCIALS_SYSTEM_PARAMS_ALL
EXPLAIN PLAN
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=177 Card=2 Bytes=304)
1 0 SORT (UNIQUE) (Cost=174 Card=2 Bytes=304)
2 1 FILTER
3 2 NESTED LOOPS (Cost=170 Card=2 Bytes=304)
4 3 NESTED LOOPS (Cost=168 Card=2 Bytes=270)
5 4 HASH JOIN (Cost=162 Card=2 Bytes=244)
6 5 MERGE JOIN (CARTESIAN) (Cost=33 Card=8 Bytes=760)
7 6 TABLE ACCESS (FULL) OF 'PER_ALL_PEOPLE_F' (Cost=9 Card=1 Bytes=66)
8 6 SORT (JOIN) (Cost=24 Card=8 Bytes=232)
9 8 TABLE ACCESS (FULL) OF 'PO_VENDORS' (Cost=24 Card=8 Bytes=232)
10 5 TABLE ACCESS (FULL) OF 'PO_HEADERS_ALL' (Cost=128 Card=232 Bytes=6264)
11 4 TABLE ACCESS (BY INDEX ROWID) OF 'PO_LINES_ALL' (Cost=3 Card=841 Bytes=10933)
12 11 INDEX (RANGE SCAN) OF 'PO_LINES_U2' (UNIQUE) (Cost=1 Card=841)
13 3 TABLE ACCESS (BY INDEX ROWID) OF 'MTL_SYSTEM_ITEMS_B' (Cost=1 Card=8444
Bytes=143548)
14 13 INDEX (UNIQUE SCAN) OF 'MTL_SYSTEM_ITEMS_B_U1' (UNIQUE)
15 2 SORT (AGGREGATE)
16 15 TABLE ACCESS (FULL) OF 'FINANCIALS_SYSTEM_PARAMS_ALL'
(Cost=2 Card=1 Bytes=5)
Thanks in advance.
Rgds
SHGoh
Followup June 22, 2004 - 8am Central time zone:
you have views in there -- i don't know what tables map to with views.
the explain plan is expecting 1 row back from per_all_people_f, you got 1,149
the explain plan is expecting 8 rows back from po_vendors, you got 3,197
assuming that is the POV table above -- the predicate:
AND pov.vendor_name BETWEEN nvl(NULL , pov.vendor_name )
AND nvl(NULL,pov.vendor_name )
(in addition to being a verbose way to say "and pov.vendor_name is not null") is assumed to return
8 rows. does that seem right? (obviously not, there is probably 3,197 rows in that table). that
is throwing it way off.
I suspect that hre is the people table -- full_name is probably almost unique, it sees
"almost_unique_column = f(x)" and is coming up with card=1
These generic predicates are killing it -- if you use bind variables, you might see something very
different. consider:
big_table@ORA9IR2> create table hre as select * from big_table where rownum <= 1149;
Table created.
big_table@ORA9IR2> create index hre_idx on hre(object_name);
Index created.
big_table@ORA9IR2> create table pov as select * from big_table where rownum <= 3197;
Table created.
big_table@ORA9IR2> create index pov_idx on pov(object_name);
Index created.
big_table@ORA9IR2>
big_table@ORA9IR2> analyze table hre compute statistics;
Table analyzed.
big_table@ORA9IR2> analyze table pov compute statistics;
Table analyzed.
big_table@ORA9IR2>
big_table@ORA9IR2> variable x varchar2(30);
big_table@ORA9IR2> set autotrace traceonly explain
big_table@ORA9IR2> select * from pov where object_name between nvl(null,object_name) and
nvl(null,object_name);
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=8 Bytes=736)
1 0 TABLE ACCESS (FULL) OF 'POV' (Cost=6 Card=8 Bytes=736)
big_table@ORA9IR2> select * from pov where object_name between nvl(:x,object_name) and
nvl(:x,object_name);
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=9 Card=88 Bytes=8096)
1 0 CONCATENATION
2 1 FILTER
3 2 TABLE ACCESS (FULL) OF 'POV' (Cost=3 Card=8 Bytes=736)
4 1 FILTER
5 4 TABLE ACCESS (BY INDEX ROWID) OF 'POV' (Cost=3 Card=8 Bytes=736)
6 5 INDEX (RANGE SCAN) OF 'POV_IDX' (NON-UNIQUE) (Cost=2 Card=1)
big_table@ORA9IR2> select * from hre where object_name = nvl(null,object_name);
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=92)
1 0 TABLE ACCESS (FULL) OF 'HRE' (Cost=3 Card=1 Bytes=92)
big_table@ORA9IR2> select * from hre where object_name = nvl(:x,object_name);
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=1150 Bytes=105800)
1 0 CONCATENATION
2 1 FILTER
3 2 TABLE ACCESS (FULL) OF 'HRE' (Cost=2 Card=1 Bytes=92)
4 1 FILTER
5 4 TABLE ACCESS (BY INDEX ROWID) OF 'HRE' (Cost=2 Card=1 Bytes=92)
6 5 INDEX (RANGE SCAN) OF 'HRE_IDX' (NON-UNIQUE) (Cost=1 Card=1)
big_table@ORA9IR2> set autotrace off
big_table@ORA9IR2>
I would suggest however, that since this seems to be a query that will be used to either accept
some values for pov to constrain the result set, or some values for hre, or maybe values for both
-- that you use a stored procedure that returns a result set via a ref cursor and use a tiny bit of
dynamic sql to construct the best predicate for each case
Like this example:
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:1288401763279
and yes, this:
and pl.item_id = msi.inventory_item_id (+)
AND msi.organization_id = 1
indicates the (+) is "not meaningful". organization_id would be null if you actually had to outer
join, null is not going to be 1, hence (+) is not useful.
Help Needed
June 30, 2004 - 5am Central time zone
Reviewer: atul from India
Hi,
Following are some queries with tkprof which are very slow
and need to tune..
Could you give some inputs
SELECT /*+FIRST_ROWS INDEX(DCGRQST DCGRQST_0U) INDEX(DCGCRC DCGCRC_0U) */
DCGRQST.POREQUESTID
FROM
DCGRQST,DCGCRC
WHERE DCGRQST.POEXCONSG LIKE '5050043RTM%'
AND
DCGRQST.POREQUESTID=DCGCRC.POREQUESTID
ORDER BY DCGRQST.POREQUESTID ASC
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 13 32.17 99.78 62622 460521 0 13
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Misses in library cache during parse: 0
Optimizer goal: FIRST_ROWS
Parsing user id: 71 (OPS$A012MTO)
Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: HINT: FIRST_ROWS
13 NESTED LOOPS
456621 TABLE ACCESS (BY ROWID) OF 'DCGRQST'
456621 INDEX (FULL SCAN) OF 'DCGRQST_0U' (UNIQUE)
13 INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'DCGCRC_0U' (UNIQUE)
================================================================================
Example 2: when run on voyage
==============================
SELECT /*+FIRST_ROWS INDEX(DCGRQST DCGRQST_0U) INDEX(DCGCRC DCGCRC_0U) */
DCGRQST.POREQUESTID
FROM
DCGRQST,DCGCRC
WHERE DCGCRC.POVOYNO LIKE 'MCU4623%'
AND DCGRQST.POREQUESTID= DCGCRC.POREQUESTID
ORDER BY DCGRQST.POREQUESTID ASC
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.02 0.05 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 13 51.36 101.87 9697 2249489 0 13
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 15 51.38 101.92 9697 2249489 0 13
Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 71 (OPS$A012MTO)
Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: HINT: FIRST_ROWS
13 NESTED LOOPS
449842 INDEX (FULL SCAN) OF 'DCGRQST_0U' (UNIQUE)
449842 TABLE ACCESS (BY ROWID) OF 'DCGCRC'
449842 INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'DCGCRC_0U' (UNIQUE)
==============
Following are the indexes available on these two table as seen from the Dancarg QA :
=======================
TABLE_NAME COLUMN_NAME INDEX_NAME
--------------- --------------- ---------------
DCGCRC POREQUESTID DCGCRC_0U
DCGCRC POVOYNO DCGCRC1_NU
DCGRQST POREQUESTID DCGRQST_4NU
DCGRQST PORECDTIME DCGRQST1
DCGRQST POAGNTRQID DCGRQST_4NU
DCGRQST POREQUESTID DCGRQST_0U
DCGRQST POPREVID DCGRQST_1NU
=====================================================
all the tables involved in the queries have data around 100000 rows..
Please give your suggestions
Followup June 30, 2004 - 10am Central time zone:
in the first one, the optimizer blindly followed your advice and used that index.
bummer, isn't it. drop the hints and come back and then we'll talk.
indexes are NOT fast=true.
Is there a better way to write this?
August 11, 2004 - 2pm Central time zone
Reviewer: DPears from Edmonton AB
Hi Tom. I saw this query over and over in an app I worked on and it bugs me. Surely there is a
better way than the subquery.
All they want is the most recent row in a series of rows created to record changes to an entity (as
identified by xxxxx in query mockup below) as it goes through its life cycle (not MY design
solution!).
select stuff
from table_a
where xxxxx and date_column = (select max(date_column)
from table_a
where xxxxx)
DB 9iR2. This is pretty generic so I left out the "create," "insert," etc stuff you requested. If
you need to know what is indexed, I can find out.
Thanks in advance.
"Gettting it faster" Oracle Magazine
August 12, 2004 - 2pm Central time zone
Reviewer: Logan Palanisamy from Santa Clara, CA USA
Tom,
In the "Getting it faster" section of your Oracle Magazine article, why are you ordering by "pet_id
DESC"? Isn't it redundant when the the where clause is "pet_id = :b1"? Or is it some kind of a
trick for the optimizer?
select weight
from (select weight
from t
where pet_id = :b1
order by pet_id DESC,
setup_date DESC,
weight DESC )
where rownum = 1;
I feel, it can be just:
select weight
from (select weight
from t
where pet_id = :b1
order by setup_date DESC,
weight DESC )
where rownum = 1;
Thanks for your wonderful service. You are a good role model for people like me.
Followup August 12, 2004 - 3pm Central time zone:
it was to help the optimizer out -- the index is on pet_id, setup_date, weight -- the order by is
redundant, but typically necessary.
Order by slows things down
August 17, 2004 - 6pm Central time zone
Reviewer: A reader
I am seeing something really weird, Oracle 9.2.0.4 all tables analyzed using dbms_stats, histograms
and everything, the full works.
I have a query that returns approx 1000 rows fine in under a second, great.
As soon as I add an ORDER BY to the query, it tanks. The plan changes completely, the tkprof and
the explain plan differ wildly, etc.
Why would a ORDER BY slow things down so much for a result set of 1000 rows only?
Thanks
Followup August 18, 2004 - 7am Central time zone:
i'd have to see an example in order to explain anything.
INDEX_COMBINE for subquery?
August 18, 2004 - 1pm Central time zone
Reviewer: A reader
Hi Tome,
I have a fairly simple example here.
create table dimtable
(col1 number(10) primary key)
/
insert into dimtable select rownum from all_tables where rownum <= 10
/
commit
/
create table bigtable
(id number(10) primary key,
col1 number(10),
col2 number(10)
)
/
insert into bigtable select rownum, mod(rownum, 1000), mod(rownum, 434) from all_objects
/
commit
/
create bitmap index b_col1_idx on
bigtable(col1)
compute statistics
/
create bitmap index b_col2_idx on
bigtable(col2)
compute statistics
/
Then I run the following query. It uses INDEX_COMBINE correctly.
SELECT /*+ INDEX_COMBINE(B B_COL1_IDX B_COL2_IDX) */
*
FROM BIGTABLE B
WHERE B.COL1 IN (1,2,3,4,5,6,7,8,9,10)
AND B.COL2 IN (23, 24, 31, 48, 78, 102, 103, 222, 225, 333, 11, 22, 3, 223, 272, 33, 342, 283,
232,122, 99 )
/
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=37 Card=2658 Bytes=3
9870)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'BIGTABLE' (Cost=37 Card=
2658 Bytes=39870)
2 1 BITMAP CONVERSION (TO ROWIDS)
3 2 BITMAP AND
4 3 BITMAP OR
5 4 BITMAP INDEX (SINGLE VALUE) OF 'B_COL1_IDX'
6 4 BITMAP INDEX (SINGLE VALUE) OF 'B_COL1_IDX'
7 4 BITMAP INDEX (SINGLE VALUE) OF 'B_COL1_IDX'
8 4 BITMAP INDEX (SINGLE VALUE) OF 'B_COL1_IDX'
9 4 BITMAP INDEX (SINGLE VALUE) OF 'B_COL1_IDX'
10 4 BITMAP INDEX (SINGLE VALUE) OF 'B_COL1_IDX'
11 4 BITMAP INDEX (SINGLE VALUE) OF 'B_COL1_IDX'
12 4 BITMAP INDEX (SINGLE VALUE) OF 'B_COL1_IDX'
13 4 BITMAP INDEX (SINGLE VALUE) OF 'B_COL1_IDX'
14 4 BITMAP INDEX (SINGLE VALUE) OF 'B_COL1_IDX'
15 3 BITMAP OR
16 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
17 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
18 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
19 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
20 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
21 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
22 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
23 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
24 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
25 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
26 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
27 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
28 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
29 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
30 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
31 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
32 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
33 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
34 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
35 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
36 15 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
However if I change one IN list to a subquery, it then failed to use INDEX_COMBINE correctly.
SELECT /*+ INDEX_COMBINE(B B_COL1_IDX B_COL2_IDX) */
*
FROM BIGTABLE B
WHERE B.COL1 IN (SELECT col1 from dimtable)
AND B.COL2 IN (23, 24, 31, 48, 78, 102, 103, 222, 225, 333, 11, 22, 3, 223, 272, 33, 342, 283,
232,122, 99 )
/
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=55 Card=21793 Bytes=
610204)
1 0 NESTED LOOPS (Cost=55 Card=21793 Bytes=610204)
2 1 INLIST ITERATOR
3 2 TABLE ACCESS (BY INDEX ROWID) OF 'BIGTABLE' (Cost=53 Card=26577 Bytes=398655)
4 3 BITMAP CONVERSION (TO ROWIDS)
5 4 BITMAP INDEX (SINGLE VALUE) OF 'B_COL2_IDX'
6 1 INDEX (UNIQUE SCAN) OF 'SYS_C0056373' (UNIQUE)
Is it possible that I can force the CBO to evaluate the subquery first and then convert the result
to an IN list of constant values and then apply the INDEX_COMBINE?
Thanks.
Followup August 18, 2004 - 11pm Central time zone:
No -- that would be like asking "run part of the query -- and then optimize the rest of it based on
what you find"
the optimizer doesn't/cant work like that.
Too long query
October 15, 2004 - 3am Central time zone
Reviewer: Rory from Philippines
Hi Tom,
Could you pls take a look at this query. It takes too long to process. And I always end up with
snaphot too old even if I've changed my undo_retention to 4hrs already. A developer needs to run
this. My last run took 4hrs but ended with snapshot too old. I'll paste the query here with the
explain plan. I could have put the statistics but then I can't finish the query.
select distinct last_name ||','|| first_name name
, TO_DATE(substr(prrv1.result_value,1,11),'YYYY/MM/DD')||':'||
SUBSTR(TO_CHAR(TO_DATE(SUBSTR(ROUND(prrv5.result_value,1),1,4),'HH24:MI'),'DD-MON-YYYY
HH24:MI'),13,3) ||
LPAD(ROUND( MOD( (prrv5.result_value * 60), 60)),2,0) ot_start
,
TO_DATE(substr(prrv4.result_value,1,11),'YYYY/MM/DD')||':'||SUBSTR(TO_CHAR(TO_DATE(SUBSTR(ROUND(prrv
6.result_value,1),1,4),'HH24:MI'),'DD-MON-YYYY HH24:MI'),13,3) ||
LPAD(ROUND( MOD( (prrv6.result_value * 60), 60)),2,0) ot_end
,prrv.run_result_id
,petf.element_type_id
,substr(paf.assignment_number,1,10)asg_no
,substr(TO_CHAR(TO_DATE(substr(prrv1.result_value,1,11),'YYYY/MM/DD'),'DAY'),1,3) DOW
,SUM(substr(prrv.result_value,1,15)) amount
,substr(prrv2.result_value,1,15) type
,substr(prrv3.result_value,1,15) hrs
,paa.assignment_id ass_id
from pay_assignment_actions paa
,pay_run_results prr
,pay_run_result_values prrv
,pay_input_values_f pivf
,pay_element_types_f petf
,pay_payroll_actions ppa
,per_time_periods ptp
,pay_element_classifications pec
/*ot_start*/
,pay_run_result_values prrv1
,pay_input_values_f pivf1
/*ot_end*/
,pay_run_result_values prrv4
,pay_input_values_f pivf4
/*ot_start_time*/
,pay_run_result_values prrv5
,pay_input_values_f pivf5
/*ot_end_time*/
,pay_run_result_values prrv6
,pay_input_values_f pivf6
/*ot_type*/
,pay_run_result_values prrv2
,pay_input_values_f pivf2
/*ot_hours*/
,pay_run_result_values prrv3
,pay_input_values_f pivf3
/*ot_end*/
,per_people_f ppf
,per_assignments_f paf
where paa.assignment_action_id = prr.assignment_action_id
and paa.assignment_id = paf.assignment_id
and paf.person_id = ppf.person_id
and prr.element_type_id = petf.element_type_id
and petf.classification_id = pec.classification_id
and prr.status IN ('P','PA')
and prr.run_result_id = prrv.run_result_id
and prrv.input_value_id = pivf.input_value_id
and pivf.element_type_id = petf.element_type_id
and paa.payroll_action_id = ppa.payroll_action_id
and ppa.payroll_id = 86
and ppa.payroll_id = ptp.payroll_id
and ppa.time_period_id = ptp.time_period_id
and trunc(PTP.end_date) BETWEEN to_date('2004/09/01','yyyy/mm/dd') and
to_date('2004/09/22','yyyy/mm/dd')
-- and UPPER(petf.element_name) = 'OVERTIME PAY'
--and petf.element_type_id in(51027,51026, 52992)
and petf.element_type_id = 150
and upper(pivf.name)= 'PAY VALUE'
/*ot_start*/
and pivf1.element_type_id = petf.element_type_id
and prrv1.input_value_id = pivf1.input_value_id
and prr.run_result_id = prrv1.run_result_id
and upper(pivf1.name) = 'START_DATE'
/*ot_end*/
and pivf4.element_type_id = petf.element_type_id
and prrv4.input_value_id = pivf1.input_value_id
and prr.run_result_id = prrv4.run_result_id
and upper(pivf4.name) = 'END_DATE'
/*ot_start_time*/
and pivf5.element_type_id = petf.element_type_id
and prrv5.input_value_id = pivf5.input_value_id
and prr.run_result_id = prrv5.run_result_id
and upper(pivf5.name) = 'START_TIME'
/*ot_end_time*/
and pivf6.element_type_id = petf.element_type_id
and prrv6.input_value_id = pivf6.input_value_id
and prr.run_result_id = prrv6.run_result_id
and upper(pivf6.name) = 'END_TIME'
/*ot_type*/
and pivf2.element_type_id = petf.element_type_id
and prrv2.input_value_id = pivf2.input_value_id
and prr.run_result_id = prrv2.run_result_id
and upper(pivf2.name)= 'OT_TYPE'
/*ot_hrs*/
and pivf3.element_type_id = petf.element_type_id
and prrv3.input_value_id = pivf3.input_value_id
and prr.run_result_id = prrv3.run_result_id
and upper(pivf3.name)= 'HOURS'
and ppf.effective_start_date = (select max(effective_start_date)
from per_people_f
where person_id = ppf.person_id)
and paf.effective_start_date = (select max(effective_start_date)
from per_assignments_f
where assignment_id = paf.assignment_id)
GROUP BY paa.assignment_id,last_name ||','|| first_name
,petf.element_type_id
,prrv.run_result_id
,substr(paf.assignment_number,1,10)
,substr(prrv2.result_value,1,15)
,substr(to_char(ptp.end_date,'YYYY/MM/DD'),1,11)
,substr(prrv3.result_value,1,15)
,
TO_DATE(substr(prrv1.result_value,1,11),'YYYY/MM/DD')||':'||SUBSTR(TO_CHAR(TO_DATE(SUBSTR(ROUND(prrv
5.result_value,1),1,4),'HH24:MI'),'DD-MON-YYYY HH24:MI'),13,3) ||
LPAD(ROUND( MOD( (prrv5.result_value * 60), 60)),2,0)
,
TO_DATE(substr(prrv4.result_value,1,11),'YYYY/MM/DD')||':'||SUBSTR(TO_CHAR(TO_DATE(SUBSTR(ROUND(prrv
6.result_value,1),1,4),'HH24:MI'),'DD-MON-YYYY HH24:MI'),13,3) ||
LPAD(ROUND( MOD( (prrv6.result_value * 60), 60)),2,0)
, substr(TO_CHAR(TO_DATE(substr(prrv1.result_value,1,11),'YYYY/MM/DD'),'DAY'),1,3)
,paa.assignment_id
/
Explain plan is
SELECT STATEMENT Cost = 112
SORT UNIQUE
SORT GROUP BY
VIEW
FILTER
SORT GROUP BY
TABLE ACCESS BY INDEX ROWID PAY_INPUT_VALUES_F
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
NESTED LOOPS
TABLE ACCESS BY INDEX ROWID
PER_TIME_PERIODS
INDEX RANGE SCAN PER_TIME_PERIODS_N50
TABLE ACCESS BY INDEX ROWID
PAY_PAYROLL_ACTIONS
INDEX RANGE SCAN
PAY_PAYROLL_ACTIONS_FK8
TABLE ACCESS BY INDEX ROWID
PAY_ASSIGNMENT_ACTIONS
INDEX RANGE SCAN
PAY_ASSIGNMENT_ACTIONS_N50
TABLE ACCESS BY INDEX ROWID
PER_ALL_ASSIGNMENTS_F
INDEX RANGE SCAN PER_ASSIGNMENTS_F_PK
SORT AGGREGATE
TABLE ACCESS BY INDEX ROWID
PER_ALL_ASSIGNMENTS_F
INDEX RANGE SCAN PER_ASSIGNMENTS_F_PK
TABLE ACCESS BY INDEX ROWID PER_ALL_PEOPLE_F
INDEX RANGE SCAN PER_PEOPLE_F_PK
TABLE ACCESS BY INDEX ROWID PER_ALL_PEOPLE_F
INDEX RANGE SCAN PER_PEOPLE_F_PK
TABLE ACCESS BY INDEX ROWID PAY_RUN_RESULTS
INDEX RANGE SCAN PAY_RUN_RESULTS_N50
TABLE ACCESS BY INDEX ROWID PAY_ELEMENT_TYPES_F
INDEX RANGE SCAN PAY_ELEMENT_TYPES_F_PK
INDEX UNIQUE SCAN PAY_ELEMENT_CLASSIFICATION_PK
TABLE ACCESS BY INDEX ROWID PAY_RUN_RESULT_VALUES
INDEX RANGE SCAN PAY_RUN_RESULT_VALUES_N50
TABLE ACCESS BY INDEX ROWID PAY_RUN_RESULT_VALUES
INDEX RANGE SCAN PAY_RUN_RESULT_VALUES_N50
TABLE ACCESS BY INDEX ROWID PAY_RUN_RESULT_VALUES
INDEX RANGE SCAN PAY_RUN_RESULT_VALUES_N50
TABLE ACCESS BY INDEX ROWID PAY_RUN_RESULT_VALUES
INDEX RANGE SCAN PAY_RUN_RESULT_VALUES_N50
TABLE ACCESS BY INDEX ROWID PAY_RUN_RESULT_VALUES
INDEX RANGE SCAN PAY_RUN_RESULT_VALUES_N50
TABLE ACCESS BY INDEX ROWID PAY_RUN_RESULT_VALUES
INDEX RANGE SCAN PAY_RUN_RESULT_VALUES_N50
TABLE ACCESS BY INDEX ROWID PAY_RUN_RESULT_VALUES
INDEX RANGE SCAN PAY_RUN_RESULT_VALUES_N50
TABLE ACCESS BY INDEX ROWID PAY_INPUT_VALUES_F
INDEX RANGE SCAN PAY_INPUT_VALUES_F_N1
TABLE ACCESS BY INDEX ROWID PAY_INPUT_VALUES_F
INDEX RANGE SCAN PAY_INPUT_VALUES_F_N1
INDEX RANGE SCAN PAY_INPUT_VALUES_F_UK2
TABLE ACCESS BY INDEX ROWID PAY_INPUT_VALUES_F
INDEX RANGE SCAN PAY_INPUT_VALUES_F_N1
TABLE ACCESS BY INDEX ROWID PAY_INPUT_VALUES_F
INDEX RANGE SCAN PAY_INPUT_VALUES_F_N1
TABLE ACCESS BY INDEX ROWID PAY_INPUT_VALUES_F
INDEX RANGE SCAN PAY_INPUT_VALUES_F_N1
INDEX RANGE SCAN PAY_INPUT_VALUES_F_N1
Thanks a lot TOm.
Followup October 15, 2004 - 11am Central time zone:
i like the design it makes when I scroll the plan up and down - swish, swoop... neat.
anyhow, when I see this:
from pay_assignment_actions paa
,pay_run_results prr
,pay_run_result_values prrv
,pay_input_values_f pivf
,pay_element_types_f petf
,pay_payroll_actions ppa
,per_time_periods ptp
,pay_element_classifications pec
/*ot_start*/
,pay_run_result_values prrv1
,pay_input_values_f pivf1
/*ot_end*/
,pay_run_result_values prrv4
,pay_input_values_f pivf4
/*ot_start_time*/
,pay_run_result_values prrv5
,pay_input_values_f pivf5
/*ot_end_time*/
,pay_run_result_values prrv6
,pay_input_values_f pivf6
/*ot_type*/
,pay_run_result_values prrv2
,pay_input_values_f pivf2
/*ot_hours*/
,pay_run_result_values prrv3
,pay_input_values_f pivf3
/*ot_end*/
,per_people_f ppf
,per_assignments_f paf
I immediately think "pivot"... so, I'll not be rewriting your query, I'll show you a TECHNIQUE you
can use and reuse over and over.......
you are simply trying to make "in record" some dimension that is currently "cross record".
So, you have a funky schema like this:
ops$tkyte@ORA9IR2> create table people( id number primary key, name varchar2(5) );
Table created.
ops$tkyte@ORA9IR2> insert into people values ( 10, 'Tom' );
1 row created.
ops$tkyte@ORA9IR2> insert into people values ( 20, 'Lori' );
1 row created.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table people_attr
2 ( id number primary key,
3 people_id references people,
4 attr_name varchar2(10),
5 val varchar2(10) )
6 /
Table created.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into people_attr values ( 1, 10, 'job', 'oracle' );
1 row created.
ops$tkyte@ORA9IR2> insert into people_attr values ( 2, 20, 'job', 'accounting' );
1 row created.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into people_attr values ( 3, 10, 'work phone', '123-4567' );
1 row created.
ops$tkyte@ORA9IR2> insert into people_attr values ( 4, 20, 'work phone', '953-2234' );
1 row created.
to report on people, their jobs and their numbers, you could:
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select people.id, people.name, pa1.val, pa2.val
2 from people,
3 people_attr pa1,
4 people_attr pa2
5 where people.id = pa1.people_id
6 and pa1.attr_name = 'job'
7 and people.id = pa2.people_id
8 and pa2.attr_name = 'work phone'
9 /
ID NAME VAL VAL
---------- ----- ---------- ----------
10 Tom oracle 123-4567
20 Lori accounting 953-2234
as you have, or you can join ONCE and pivot:
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select people.id, people.name,
2 max( decode( pa.attr_name, 'job', pa.val ) ),
3 max( decode( pa.attr_name, 'work phone', pa.val ) )
4 from people,
5 people_attr pa
6 where people.id = pa.people_id
7 group by people.id, people.name
8 /
ID NAME MAX(DECODE MAX(DECODE
---------- ----- ---------- ----------
10 Tom oracle 123-4567
20 Lori accounting 953-2234
try the pivot
sorry for posting it here but need this clarifications
October 15, 2004 - 12pm Central time zone
Reviewer: Raj from IN
I read following in the http://www.fortunecity.com/skyscraper/oracle/699/orahtml/hayden/libcache.html
Reducing Library Cache Misses
You can reduce library cache misses by
Allocating Additional Memory for the Library Cache
You may be able to reduce library cache misses on execution calls by allocating additional memory
for the library cache. To ensure that shared SQL areas remain in the cache once their SQL
statements are parsed, increase the amount of memory available to the library cache until the
V$LIBRARYCACHE.RELOADS value is near 0. To increase the amount of memory available to the library
cache, increase the value of the initialization parameter SHARED_POOL_SIZE. The maximum value for
this parameter depends on your operating system. This measure will reduce implicit reparsing of SQL
statements and PL/SQL blocks on execution. To take advantage of additional memory available for
shared SQL areas, you may also need to increase the number of cursors permitted for a session. You
can increase this limit by increasing the value of the initialization parameter OPEN_CURSORS. Be
careful not to induce paging and swapping by allocating too much memory for the library cache. The
benefits of a library cache large enough to avoid cache misses can be partially offset by reading
shared SQL areas into memory from disk whenever you need to access them.
Writing Identical SQL Statements
You may be able to reduce library cache misses on parse calls by ensuring that SQL statements and
PL/SQL blocks share a shared SQL area whenever possible. For two different occurrences of a SQL
statement or PL/SQL block to share a shared SQL area, they must be identical according to these
criteria:
The text of the SQL statements or PL/SQL blocks must be identical, character for character,
including spaces and case. For example, these statements cannot use the same shared SQL area:
SELECT * FROM emp;
SELECT * FROM emp;
These statements cannot use the same shared SQL area:
SELECT * FROM emp;
SELECT * FROM Emp;
References to schema objects in the SQL statements or PL/SQL blocks must resolve to the same object
in the same schema. For example, if the schemas of the users BOB and ED both contain an EMP table
and both users issue the following statement, their statements cannot use the same shared SQL area:
SELECT * FROM emp;
SELECT * FROM emp;
If both statements query the same table and qualify the table with the schema, as in the following
statement, then they can use the same shared SQL area: SELECT * FROM bob.emp;
Bind variables in the SQL statements must match in name and datatype. For example, these statements
cannot use the same shared SQL area: SELECT * FROM emp WHERE deptno = :department_no; SELECT * FROM
emp WHERE deptno = :d_no;
The SQL statements must be optimized using the same optimization approach and, in the case of the
cost-based approach, the same optimization goal. For information on optimization approach and goal,
see Chapter 9, "Tuning SQL Statements". Shared SQL areas are most useful for reducing library cache
misses for multiple users running the same application. Discuss these criteria with the developers
of such applications and agree on strategies to ensure that the SQL statements and PL/SQL blocks of
an application can use the same shared SQL areas:
Use bind variables rather than explicitly specified constants in your statements whenever possible.
For example, the following two statements cannot use the same shared area because they do not match
character for character: SELECT ename, empno FROM emp WHERE deptno = 10; SELECT ename, empno FROM
emp WHERE deptno = 20; You can accomplish the goals of these statements by using the following
statement that contains a bind variable, binding 10 for one occurrence of the statement and 20 for
the other: SELECT ename, empno FROM emp WHERE deptno = :department_no; The two occurrences of the
statement can use the same shared SQL area.
Be sure that individual users of the application do not change the optimization approach and goal
for their individual sessions. You can also increase the likelihood that SQL statements issued by
different applications can share SQL areas by establishing these policies among the developers of
these applications:
Standardize naming conventions for bind variables and spacing conventions for SQL statements and
PL/SQL blocks.
Use stored procedures whenever possible. Multiple users issuing the same stored procedure
automatically use the same shared PL/SQL area. Since stored procedures are stored in a parsed form,
they eliminate runtime parsing altogether.
Does this mean that to in order to soft parse a query they have to completely identical even in
terms of spaces, case
I always thought that if query is not using bind variables or the where conditions is different
then Oracle does a hard parse
Thanks
Followup October 15, 2004 - 3pm Central time zone:
if two queries match in every detail -- they can be shared.
if they do not, they won't and they will hard parse the first time they are executed.
(why would you copy an entire web page -- that is what the link is for...)
the existence of bind variables does not affect the algorithm. If you execute
"select * from dual"
a billion times, there will be a hard parse the first time and soft parses after that. NO BINDS
THERE.
you use binds when you are executing a query that differs ONLY in its inputs:
select * from emp where empno = 1
select * from emp where empno = 2
...
select * from emp where empno = 1000000000
those are all "unique" sql's - but:
select * from emp where empno = :x
with the inputs 1, 2, ..., 1000000000 is a single reusable sql statement.
hmm..
October 15, 2004 - 5pm Central time zone
Reviewer: A reader
"select * from emp where empno = :x
with the inputs 1, 2, ..., 1000000000 is a single reusable sql statement. "
the size of the input also matters as we have seen
elsewhere in your site (I think.) But in general the above
would be a reusable statement for a range of :x values.
Instead of getting one hard parse, you would
get perhaps, say 3 hard parses for different size ranges
(e.g. for x from 1 to 40000, one hard parse and so on - note
that these numbers are totally imaginary.)
Still much better than having 1000000000 unique statements.
Followup October 15, 2004 - 6pm Central time zone:
well, i tricked you didn't i :)
I used a number, not a string....
yes, it could result in a few shareable sql statements based on bind input sizes.....
(keep me honest -- excellent, don't stop... shows me people are watching :)
ops$tkyte@ORA9IR2> drop table t;
Table dropped.
ops$tkyte@ORA9IR2> alter system flush shared_pool;
System altered.
ops$tkyte@ORA9IR2> create table t ( x int );
Table created.
ops$tkyte@ORA9IR2> variable n number
ops$tkyte@ORA9IR2> exec :n := 1;
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> select * from t where x = :n;
no rows selected
ops$tkyte@ORA9IR2> exec :n := to_number(rpad('9',38,'9'))
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> select * from t where x = :n;
no rows selected
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select sql_text from v$sql where sql_text like 'select * from t where x = %';
SQL_TEXT
-------------------------------------------------------------------------------
select * from t where x = :n
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable s varchar2(1)
ops$tkyte@ORA9IR2> exec :s := 1;
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> select * from t where x = :s;
no rows selected
ops$tkyte@ORA9IR2> variable s varchar2(38)
ops$tkyte@ORA9IR2> exec :s := to_number(rpad('9',38,'9'))
PL/SQL procedure successfully completed.
ops$tkyte@ORA9IR2> select * from t where x = :s;
no rows selected
ops$tkyte@ORA9IR2> select sql_text from v$sql where sql_text like 'select * from t where x = %';
SQL_TEXT
-------------------------------------------------------------------------------
select * from t where x = :s
select * from t where x = :s
select * from t where x = :n

October 15, 2004 - 6pm Central time zone
Reviewer: A reader
Not sure I understand.
"yes, it could result in a few shareable sql statements based on bind input sizes....."
What does the bind input size have to do here?
select from t where empno = :x
will not be hard parsed regardless of the actual value of the bind variable passed in, right? Why
did the previous reader say that there will be hard parses for different size ranges?
Followup October 16, 2004 - 10am Central time zone:
with a number correct.
with a string -- well, see the above example. i showed (not just having a reader saying) that
string sizes "count" -- different bind lengths for strings (there are 'steps' like 32 is a 'step'
-- bind a string < 32 charcters -- one child cursor, bind a string >= 32 characters -- another
child cursor).
A number is a number however, we don't need different setups for them. For strings -- we have
different child sql statements for different ranges (as demonstrated right above with two :s
queries)
v$sql_shared_cursor
October 15, 2004 - 7pm Central time zone
Reviewer: Winston from Canada
I could reproduce the test case and I noticed that hard parse did happen from the sql trace file.
I also checked v$sql_shared_cursor and found out the reason that sharing did not happen was
BIND_MISMATCH.
BIND_MISMATCH
VARCHAR2(1)
(Y|N) The bind metadata does not match the existing child cursor
Followup October 16, 2004 - 10am Central time zone:
correct, that is exactly what I was showing - it is supposed to happen that way.
too long query
October 17, 2004 - 11pm Central time zone
Reviewer: Rory from Philippines
Hi tom,
Thanks for the "pivot query". I dont know how to
say this but would it be possible for you to rewrite
my query above using the pivot that you mentioned.
But with that, I'll be able to understand it.
My query is the one with the swish, swoop explain plan.
Hope you'll have the time. I really need the sql badly
and the developer doesnt have an idea on pivot.
Thanks a lot.
Followup October 18, 2004 - 7am Central time zone:
you cannot read the above and figure it out?
I showed you a technique -- JOIN ONCE, use decode to move the data out into columns and then group
by. The technique above must be understood by you before you can use it (just a picky thing about
me I guess -- before someone uses something I believe they should understand what it is, how it
works, what it does).
Soooo -- apply the technique to your query. As I don't have your tables, your data, etc -- I might
make a mistake, one that you would not detect unless you yourself understand what this is doing and
how it does it.
sql profile in 10g
October 19, 2004 - 4pm Central time zone
Reviewer: James
Hope you don't mind asking me this in this thread.
In 10g, If I used sql tuning advisor to tune a sql statement including creating sql profile for
that statement, how do I remove that profile if next time when the sql statement is run the profile
that was generated eariler should not be used. Is there a way to remove sql profiles? Thanks.
SQL Tuning
October 25, 2004 - 11am Central time zone
Reviewer: Thirumaran from INDIA
Hi Tom,
When thinking about query tuning i have some doubts when a query is joined with 2 or more tables .
version 9i
Example:
Select empno,ename,dname,loc from emp e,dept d
where e.deptno = d.deptno
and salary > 8000
and job not in ('CLERK')
1) using table alias name for columns will speed up the query processing or is just only for
readability.
i.e Select e.empno,e.ename,d.dname,d.loc instead of
Select empno,ename,dname,loc as mentioned above.
2) where e.deptno = d.deptno
A) will Oracle read from Right to Left or Left to Right .
my understanding is it read from Left to Right (am i right)
B) I generall put small tables to left and Big table to right (is this a good method of writing
queries)
3) How will oracle read the query from top to bottom or Bottom to top or the cost based optimizer
wil take care.
i.e (as per the above query)
will it read joins first then read the salary > 1000 and then job not in ('CLERK')
or the reverse ways.
4) When should i use suquery and when should i use joins
i read an article you can change Subquery to a join but not the reverse . Why ?
Thanks ,
Thirumaran
Followup October 25, 2004 - 11am Central time zone:
1) readability
2) either or, the job of the optimizer is to figure out which table to drive with. you don't need
to concern yourself with "what order should the things in a predicate be specified" like that.
3) CBO doesn't care really, rbo did, but the CBO doesn't.
4) use a subquery when you don't need the columns in your result set (when you don't select from
it), use a join otherwise. the job of the optimizer is to figure out if the subquery should be
written as a join (it does that)
query tuning
October 29, 2004 - 12pm Central time zone
Reviewer: Sree from USA
Hi Tom,
This query is running very slow and some time it does not.
Table WMS_INV_UNITS_TRANS is our transaction table which has 9 M and growing.
I think in-line query with max(iut_id) is making the damage.
Is their any other way to rewrite the query to run fast.
Thanks Sree
SELECT loc_code, i.item_code, i.item_descr, i.grade_code,
SUM (trans_qty) balance_qty, aprv_cust_code
FROM (
SELECT
(SELECT i1.i_id
FROM wms_inv_units_trans i1
WHERE i1.iut_id = trans_iut_id) i_id,
(SELECT aprv_cust_code
FROM wms_inv_units_trans ap1
WHERE ap1.iut_id = trans_iut_id) aprv_cust_code,
DECODE (trans_code,
'TRANSFEROT', new_balance_qty * 1,
'SHIPPED', new_balance_qty * -1,
'TRANSFERIN', trans_qty,
trans_qty
) trans_qty,
NVL (zone_loc_code_dnmlz,
SUBSTR (zone_code_dnmlz, 1, 3)
) loc_code
FROM wms_inv_units_trans iu,
(SELECT
iu.iu_id trans_iu_id, MAX (iu.iut_id) trans_iut_id
FROM wms_inv_units_trans iu
WHERE iu.zone_loc_code_dnmlz LIKE '010'
AND iu.trans_effective_ts <=
TO_DATE ('10/25/2004 060059',
'mm/dd/rrrr hh24miss'
)
AND iu.item_type_code_dnmlz = 'BRITE'
GROUP BY iu.iu_id) iu1
WHERE 1 = 1
AND iu.iu_id = iu1.trans_iu_id
AND iu.zone_loc_code_dnmlz LIKE '010'
AND iu.trans_effective_ts <=
TO_DATE ('10/25/2004 060059', 'mm/dd/rrrr hh24miss')
AND iu.item_type_code_dnmlz = 'BRITE'
UNION ALL
SELECT
(SELECT i1.i_id
FROM wms_inv_units_trans i1
WHERE i1.iut_id = trans_iut_id) i_id,
(SELECT aprv_cust_code
FROM wms_inv_units_trans ap1
WHERE ap1.iut_id = trans_iut_id) aprv_cust_code,
DECODE (trans_code,
'TRANSFEROT', new_balance_qty * -1,
'SHIPPED', new_balance_qty * -1,
'TRANSFERIN', trans_qty,
trans_qty
) trans_qty,
iu.transfer_from_loc_code_dnmlz loc_code
FROM wms_inv_units_trans iu,
(SELECT
iu.iu_id trans_iu_id, MAX (iu.iut_id) trans_iut_id
FROM wms_inv_units_trans iu
WHERE iu.transfer_from_loc_code_dnmlz LIKE '010'
AND iu.trans_effective_ts <=
TO_DATE ('10/25/2004 060059',
'mm/dd/rrrr hh24miss'
)
AND iu.item_type_code_dnmlz = 'BRITE'
AND iu.trans_code = 'TRANSFEROT'
GROUP BY iu.iu_id) iu1
WHERE iu.iu_id = iu1.trans_iu_id
AND iu.transfer_from_loc_code_dnmlz LIKE '010'
AND iu.trans_effective_ts <=
TO_DATE ('10/25/2004 060059', 'mm/dd/rrrr hh24miss')
AND iu.item_type_code_dnmlz = 'BRITE'
AND iu.trans_code = 'TRANSFEROT') a,wms_items i
WHERE 1 = 1 AND i.i_id = a.i_id
GROUP BY loc_code,
item_code,
item_descr,
grade_code,
aprv_cust_code
HAVING SUM (trans_qty) <> 0
ORDER BY 1,2
Explain plan
------------
Operation Object Name Rows Bytes Cost Object Node In/Out PStart PStop
SELECT STATEMENT Optimizer Mode=CHOOSE 755 100509
FILTER
SORT GROUP BY 755 60 K 100509
HASH JOIN 15 K 1 M 100398
VIEW 15 K 618 K 100334
UNION-ALL
HASH JOIN 15 K 998 K 66704
VIEW 14 K 380 K 33384
SORT GROUP BY 14 K 380 K 33384
TABLE ACCESS FULL GEM.WMS_INV_UNITS_TRANS 15 K 381 K 33308
TABLE ACCESS FULL GEM.WMS_INV_UNITS_TRANS 15 K 616 K 33308
TABLE ACCESS BY INDEX ROWID GEM.WMS_INV_UNITS_TRANS 1 33 8
NESTED LOOPS 44 2 K 33630
VIEW 45 1 K 33313
SORT GROUP BY 45 1 K 33313
TABLE ACCESS FULL GEM.WMS_INV_UNITS_TRANS 45 1 K 33308
INDEX RANGE SCAN GEM.IUT_IU_COMP_IDX 5 2
TABLE ACCESS FULL GEM.WMS_ITEMS 25 K 1001 K 41
Followup October 29, 2004 - 4pm Central time zone:
yes, instead of self joining back with max(dt) by id
select *
from ( select ..., MAX(dt) over (partition by id) max_dt
from ...
where ...)
where dt = max_dt;
Oracle 9204
November 3, 2004 - 3pm Central time zone
Reviewer: A reader
Tom,
I have a query like this
INSERT INTO d
(SELECT a.*, sysdate
FROM a, vw_a b
WHERE a.cnum = b.cnum
AND a.idnum = b.idnum)
and the view vw_a is
select cnum, max(idnum) as idnum from a group by cnum
having count(*) > 1
Can you show me how to modify this query in a better way.
Thanks.
Followup November 5, 2004 - 10am Central time zone:
so, insert the row from A having the max IDNUM in any group of CNUMS having more than one record
select idnum, cnum, ...., sysdate
from (
select a.*,
count(*) over (partion by cnum) cnt,
max(idnum) over (partition by cnum) maxid
from a
)
where cnt > 1
and idnum = maxid
Update in a loop - SQL tuning?
November 4, 2004 - 5am Central time zone
Reviewer: A reader
run_resultid INTEGER;
...
FOR matrixvalue IN (SELECT mv.POSITION, mv.proposed
FROM matrix_type mt,
matrix_values mv,
matrix m,
RESULT r
WHERE r.result_id =
run_resultid
AND m.user_id = r.user_id
AND m.matrix_type_id =
mt.matrix_type_id
AND mt.op = 3
AND mv.matrix_id =
m.matrix_id
AND m.matrix_id IN (
SELECT mre.matrix_id
FROM matrix_result mre
WHERE mre.result_id =
run_resultid)
ORDER BY mv.POSITION ASC)
LOOP
xaxisvalue := sf_getaxisvalue (
run_resultid,
3,
'X',
MOD (
matrixvalue.POSITION,
sf_getmatrixcolcount (3)
)
);
yaxisvalue := sf_getaxisvalue (
run_resultid,
3,
'Y',
FLOOR (
matrixvalue.POSITION /
sf_getmatrixrowcount (3)
)
);
UPDATE result_table rt
SET rt.op = 3,
rt.done = 1,
rt.proposed = matrixvalue.proposed
WHERE rt.done = 0
AND rt.result_id = run_resultid
AND rt.product_id IN (
SELECT p.product_id
FROM product p, global_prod_data gkd
WHERE p.product_serial_no =
gkd.product_serial_no
AND p.package_code =
gkd.package_code
AND gkd.spl >= xaxisvalue)
AND p.sales_frequency_accumulated >=
yaxisvalue
);
END LOOP;
There are indexes on columns package_code and product_serial_no in tables p and gkd, and also on
the result_id in the rt table.
What i have:
I have a subset of products from table p which are stored in table rt - on which a number of
calculations are to be made using values contained in matrices. The data to the products are stored
on local warehouse level in the p table and global product variables are stored in the gkd table.
For reference the no of rows of the tables are something like; p: ~40,000 * 30, gkd 120,000, rt:
could reach 40,000 * 3000. Although for this run we would only be interested in 40,000 records of
each of course.
What I need:
I need to loop through all the matrix values and do this for each value present in the matrix (I
compare with the headers in the matrix (i.e. X, Y) and update the table with the code in the matrix
itself.) I need to loop from either high value in X or Y - This is a typical matrix with X/Y values
and codes:
Y/X 175 150 130 110
8 C C C C
6 C N S
5 D C
4 E
Although I am using indexes I think that this could tune a little bit more... but after a number of
tests I only get it going slower than this.
Is there any possibility that you could take a peek and hint on this poor one.
Thanks!
/Tony
Followup November 5, 2004 - 11am Central time zone:
sorry -- too complex and incomplete for a review/followup.
I cannot run the example and it is bigger than a breadbox (so not appropriate here)
sql tuning
November 4, 2004 - 1pm Central time zone
Reviewer: sree from USA
Hi Tom,
I changed query as you advised above. It is still taking too long (may be 20 min) on our
testdatabse. It does not run at all in our production database. Here is the trace file. could you
please help me to optimize the query.
Thanks
SELECT loc_code, i.item_code, i.item_descr, i.grade_code,
SUM (trans_qty) balance_qty, aprv_cust_code
FROM (
SELECT /*+ rule */
(SELECT i1.i_id
FROM wms_inv_units_trans i1
WHERE i1.iut_id = trans_iut_id) i_id,
(SELECT aprv_cust_code
FROM wms_inv_units_trans ap1
WHERE ap1.iut_id = trans_iut_id) aprv_cust_code,
DECODE (trans_code,
'TRANSFEROT', new_balance_qty * 1,
'SHIPPED', new_balance_qty * -1,
'TRANSFERIN', trans_qty,
trans_qty
) trans_qty,
NVL (zone_loc_code_dnmlz,
SUBSTR (zone_code_dnmlz, 1, 3)
) loc_code
FROM wms_inv_units_trans iu,
(SELECT /*+ rule */
iu.iu_id trans_iu_id,
MAX (iu.iut_id) over(partition by iu.i
u_id) trans_iut_id
FROM wms_inv_units_trans iu
WHERE iu.zone_loc_code_dnmlz LIKE '010'
AND iu.trans_effective_ts <=
TO_DATE ('10/25/2004 060059',
'mm/dd/rrrr hh24miss'
)
AND iu.item_type_code_dnmlz = 'BRITE'
) iu1
WHERE 1 = 1
AND iu.iu_id = iu1.trans_iu_id
AND iu.zone_loc_code_dnmlz LIKE '010'
AND iu.trans_effective_ts <=
TO_DATE ('10/25/2004 060059', 'mm/dd/rrrr hh24miss')
AND iu.item_type_code_dnmlz = 'BRITE'
UNION ALL
SELECT /*+ rule */
(SELECT i1.i_id
FROM wms_inv_units_trans i1
WHERE i1.iut_id = trans_iut_id) i_id,
(SELECT aprv_cust_code
FROM wms_inv_units_trans ap1
WHERE ap1.iut_id = trans_iut_id) aprv_cust_code,
DECODE (trans_code,
'TRANSFEROT', new_balance_qty * -1,
'SHIPPED', new_balance_qty * -1,
'TRANSFERIN', trans_qty,
trans_qty
) trans_qty,
iu.transfer_from_loc_code_dnmlz loc_code
FROM wms_inv_units_trans iu,
(SELECT /*+ rule */
iu.iu_id trans_iu_id,
MAX (iu.iut_id) over(partition by iu.i
u_id) trans_iut_id
FROM wms_inv_units_trans iu
WHERE iu.transfer_from_loc_code_dnmlz LIKE '010'
AND iu.trans_effective_ts <=
TO_DATE ('10/25/2004 060059',
'mm/dd/rrrr hh24miss'
)
AND iu.item_type_code_dnmlz = 'BRITE'
AND iu.trans_code = 'TRANSFEROT') iu1
WHERE iu.iu_id = iu1.trans_iu_id
AND iu.transfer_from_loc_code_dnmlz LIKE '010'
AND iu.trans_effective_ts <=
TO_DATE ('10/25/2004 060059', 'mm/dd/rrrr hh24miss')
AND iu.item_type_code_dnmlz = 'BRITE'
AND iu.trans_code = 'TRANSFEROT') a,wms_items i
WHERE 1 = 1 AND i.i_id = a.i_id
GROUP BY loc_code,
item_code,
item_descr,
grade_code,
aprv_cust_code
HAVING SUM (trans_qty) <> 0
ORDER BY 1,2
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ----------
----------
Parse 1 0.07 0.09 0 589 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 44 406.16 973.64 468772 17776508 167 632
------- ------ -------- ---------- ---------- ---------- ----------
----------
total 46 406.23 973.73 468772 17777097 167 632
Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 60
Rows Row Source Operation
------- ---------------------------------------------------
632 FILTER (cr=17776508 r=468772 w=21012 time=973639247 us)
776 SORT GROUP BY (cr=17776508 r=468772 w=21012 time=973637989 us)
4241035 NESTED LOOPS (cr=17776508 r=468772 w=21012 time=944477872 us)
4241035 VIEW (cr=9294289 r=468595 w=21012 time=858818518 us)
4241035 UNION-ALL (cr=9294289 r=468595 w=21012 time=852951631 us)
4232223 TABLE ACCESS BY INDEX ROWID OBJ#(30440) (cr=7752390 r=362248 w=208
71 time=700601655 us)
7007145 NESTED LOOPS (cr=1839250 r=153738 w=20871 time=407192972 us)
823659 VIEW (cr=177047 r=136972 w=20871 time=306200149 us)
823659 WINDOW SORT (cr=177047 r=136972 w=20871 time=305048310 us)
823659 TABLE ACCESS BY INDEX ROWID OBJ#(30440) (cr=177045 r=116098 w=
0 time=230171464 us)
823659 INDEX RANGE SCAN OBJ#(38441) (cr=4162 r=4162 w=0 time=3675397
us)(object id 38441)
6183485 INDEX RANGE SCAN OBJ#(31334) (cr=1662203 r=16766 w=0 time=818803
93 us)(object id 31334)
8812 NESTED LOOPS (cr=322077 r=43784 w=141 time=47854415 us)
8784 VIEW (cr=40230 r=39818 w=141 time=44789200 us)
8784 WINDOW SORT (cr=40230 r=39818 w=141 time=44776718 us)
8784 TABLE ACCESS BY INDEX ROWID OBJ#(30440) (cr=40230 r=39676 w=0 t
ime=44383665 us)
554880 INDEX RANGE SCAN OBJ#(37831) (cr=2367 r=2367 w=0 time=8335855
us)(object id 37831)
8812 TABLE ACCESS BY INDEX ROWID OBJ#(30440) (cr=281847 r=3966 w=0 tim
e=2976580 us)
10150 AND-EQUAL (cr=271697 r=3128 w=0 time=2754523 us)
73943 INDEX RANGE SCAN OBJ#(31334) (cr=145313 r=1612 w=0 time=1231489
us)(object id 31334)
63835 INDEX RANGE SCAN OBJ#(37831) (cr=126384 r=1516 w=0 time=1297948
us)(object id 37831)
4241035 TABLE ACCESS BY INDEX ROWID OBJ#(30444) (cr=8482219 r=177 w=0 time=6
3070435 us)
4241035 INDEX UNIQUE SCAN OBJ#(30451) (cr=4241037 r=24 w=0 time=30921961 us
)(object id 30451)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 44 0.00 0.00
db file sequential read 447756 0.06 566.99
direct path write 8 0.02 0.03
direct path read 7109 2.36 40.83
SQL*Net message from client 44 837.84 838.10
********************************************************************************
OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 3 0.10 0.10 1 596 0 0
Execute 4 0.04 0.05 5 255 0 0
Fetch 45 406.16 973.64 468772 17776511 167 633
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 52 406.30 973.80 468778 17777362 167 633
Misses in library cache during parse: 3
Misses in library cache during execute: 1
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 47 0.00 0.00
SQL*Net message from client 47 837.84 843.60
db file sequential read 447757 0.06 567.00
SQL*Net more data from client 1 0.00 0.00
direct path write 8 0.02 0.03
direct path read 7109 2.36 40.83
OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 63 0.04 0.05 1 4 0 0
Execute 169 0.02 0.01 0 3 0 1
Fetch 422 0.03 0.08 39 666 0 347
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 654 0.09 0.15 40 673 0 348
Misses in library cache during parse: 19
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
db file sequential read 39 0.01 0.06
4 user SQL statements in session.
63 internal SQL statements in session.
67 SQL statements in session.
0 statements EXPLAINed in this session.
********************************************************************************
Trace file: gemtest_ora_75344.trc
Trace file compatibility: 9.00.01
Sort options: default
1 session in tracefile.
4 user SQL statements in trace file.
63 internal SQL statements in trace file.
67 SQL statements in trace file.
23 unique SQL statements in trace file.
456266 lines in trace file.
Followup November 5, 2004 - 2pm Central time zone:
you didn't do it the way I told you.
it has rule hints in it, i would never say to do that.
sql tuning
November 9, 2004 - 9am Central time zone
Reviewer: Sree from USA
Hi Tom,
I changed the query as you told with cost based.
Still it takes 4 to 7 minutes depending up on the data and the load.
I would appreciate if you help me to improve the performance.
Here is the tkprof attached.
Thanks Sree
**************************************************************
TKPROF: Release 9.2.0.4.0 - Production on Mon Nov 8 17:00:16 2004
Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
Trace file: gemprod_ora_3469512.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
Misses in library cache during execute: 1
Optimizer goal: CHOOSE
Parsing user id: SYS
********************************************************************************
select obj#,type#,ctime,mtime,stime,status,dataobj#,flags,oid$, spare1,
spare2
from
obj$ where owner#=:1 and name=:2 and namespace=:3 and remoteowner is null
and linkname is null and subname is null
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 4 0.00 0.02 1 8 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 9 0.00 0.02 1 8 0 0
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS (recursive depth: 1)
********************************************************************************
select condition
from
cdef$ where rowid=:1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 40 0.00 0.00 0 0 0 0
Execute 40 0.00 0.00 0 0 0 0
Fetch 40 0.00 0.00 0 80 0 40
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 120 0.00 0.00 0 80 0 40
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 CDEF$ (cr=1 r=0 w=0 time=24 us)
********************************************************************************
SELECT loc_code, (SELECT loc_name
FROM wms_locations l1
WHERE l1.loc_code = a.loc_code) loc_name,
LTRIM (RTRIM (SUBSTR (s, 1, 20))) item_type_code,
item_code my_item_code, i.item_code, i.item_descr, i.grade_code,
pkg_report.fnc_balance_conversion (SUM (trans_qty),
'EXTERNAL CONVERSION',
external_conv_factor
) balance_qty,
pkg_report.fnc_uom_conversion
('EXTERNAL CONVERSION',
LTRIM (RTRIM (SUBSTR (s, 181, 20))),
external_conv_factor
) inv_uom_code,
LTRIM (RTRIM (SUBSTR (s, 21, 40))) aprv_cust_code,
LTRIM (RTRIM (SUBSTR (s, 61, 40))) quality_status_dnmlz
FROM (SELECT iu.iu_id, iut_id, (SELECT i1.i_id
FROM wms_inv_units_trans i1
WHERE i1.iut_id = trans_iut_id) i_id,
(SELECT RPAD (NVL (item_type_code_dnmlz, ' '), 20, ' ')
|| RPAD (NVL (aprv_cust_code, ' '), 40, ' ')
|| RPAD (NVL (quality_status_dnmlz, ' '), 40, ' ')
|| RPAD (NVL (zone_code_dnmlz, ' '), 40, ' ')
|| RPAD (NVL (quality_confirmed_flag, ' '), 40, ' ')
|| RPAD (NVL (inv_uom_code_dnmlz, ' '), 20, ' ')
|| RPAD (NVL (lot_code, ' '), 20, ' ')
FROM wms_inv_units_trans s1
WHERE s1.iut_id = trans_iut_id) s,
DECODE (trans_code,
'TRANSFEROT', new_balance_qty * 1,
'SHIPPED', new_balance_qty * -1,
'TRANSFERIN', trans_qty,
trans_qty
) trans_qty,
NVL (zone_loc_code_dnmlz,
SUBSTR (zone_code_dnmlz, 1, 3)
) loc_code
FROM wms_inv_units_trans iu,
(SELECT trans_iu_id, trans_iut_id
FROM (SELECT iu_id trans_iu_id, iut_id,
MAX (iut_id) OVER (PARTITION BY iu_id)
trans_iut_id
FROM wms_inv_units_trans iu
WHERE iu.zone_loc_code_dnmlz LIKE
UPPER (REPLACE ('043', '*', '%'))
AND iu.trans_effective_ts <=
TO_DATE ('11/05/2004 060059',
'mm/dd/rrrr hh24miss')
AND iu.item_type_code_dnmlz LIKE
REPLACE ('BRITE', '*', '%'))
WHERE iut_id = trans_iut_id) iu1
WHERE 1 = 1
AND iu.iu_id = iu1.trans_iu_id
AND iu.zone_loc_code_dnmlz LIKE
UPPER (REPLACE ('043', '*', '%'))
AND iu.trans_effective_ts <=
TO_DATE ('11/05/2004 060059', 'mm/dd/rrrr hh24miss')
AND iu.item_type_code_dnmlz LIKE REPLACE ('BRITE', '*', '%')
UNION ALL
SELECT iu.iu_id, iut_id, (SELECT i1.i_id
FROM wms_inv_units_trans i1
WHERE i1.iut_id = trans_iut_id) i_id,
(SELECT RPAD (NVL (item_type_code_dnmlz, ' '), 20, ' ')
|| RPAD (NVL (aprv_cust_code, ' '), 40, ' ')
|| RPAD (NVL (quality_status_dnmlz, ' '), 40, ' ')
|| RPAD (NVL (zone_code_dnmlz, ' '), 40, ' ')
|| RPAD (NVL (quality_confirmed_flag, ' '), 40, ' ')
|| RPAD (NVL (inv_uom_code_dnmlz, ' '), 20, ' ')
|| RPAD (NVL (lot_code, ' '), 20, ' ')
FROM wms_inv_units_trans s1
WHERE s1.iut_id = trans_iut_id) s,
DECODE (trans_code,
'TRANSFEROT', new_balance_qty * -1,
'SHIPPED', new_balance_qty * -1,
'TRANSFERIN', trans_qty,
trans_qty
) trans_qty,
iu.transfer_from_loc_code_dnmlz loc_code
FROM wms_inv_units_trans iu,
(SELECT trans_iu_id, trans_iut_id
FROM (SELECT iu_id trans_iu_id, iut_id,
MAX (iut_id) OVER (PARTITION BY iu_id)
trans_iut_id
FROM wms_inv_units_trans iu
WHERE iu.transfer_from_loc_code_dnmlz LIKE
UPPER (REPLACE ('043', '*', '%'))
AND iu.trans_effective_ts <=
TO_DATE ('11/05/2004 060059',
'mm/dd/rrrr hh24miss')
AND iu.item_type_code_dnmlz LIKE
REPLACE ('BRITE', '*', '%')
AND iu.trans_code = 'TRANSFEROT')
WHERE iut_id = trans_iut_id) iu1
WHERE iu.iu_id = iu1.trans_iu_id
AND iu.transfer_from_loc_code_dnmlz LIKE
UPPER (REPLACE ('043', '*', '%'))
AND iu.trans_effective_ts <=
TO_DATE ('11/05/2004 060059', 'mm/dd/rrrr hh24miss')
AND iu.item_type_code_dnmlz LIKE REPLACE ('BRITE', '*', '%')
AND iu.trans_code = 'TRANSFEROT') a,
wms_items i
WHERE 1 = 1 AND i.i_id = a.i_id
GROUP BY loc_code,
LTRIM (RTRIM (SUBSTR (s, 1, 20))),
item_code,
item_code,
item_descr,
grade_code,
LTRIM (RTRIM (SUBSTR (s, 181, 20))),
LTRIM (RTRIM (SUBSTR (s, 21, 40))),
LTRIM (RTRIM (SUBSTR (s, 61, 40))),
external_conv_factor
HAVING SUM (trans_qty) <> 0
ORDER BY 2, 3, 4
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 10 0.00 197.18 196521 2866079 7 124
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 12 0.00 197.23 196521 2866079 7 124
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS
Rows Row Source Operation
------- ---------------------------------------------------
1 TABLE ACCESS BY INDEX ROWID WMS_LOCATIONS (cr=2 r=0 w=0 time=18 us)
1 INDEX UNIQUE SCAN L_LOC_CODE_UK (cr=1 r=0 w=0 time=11 us)(object id 30482)
124 SORT ORDER BY (cr=2866079 r=196521 w=5321 time=197186405 us)
124 FILTER (cr=2866077 r=196521 w=5321 time=197184041 us)
184 SORT GROUP BY (cr=2866077 r=196521 w=5321 time=197183887 us)
422165 HASH JOIN (cr=2866077 r=196521 w=5321 time=194089003 us)
25671 TABLE ACCESS FULL WMS_ITEMS (cr=411 r=0 w=0 time=25046 us)
422165 VIEW (cr=2865666 r=196521 w=5321 time=193133938 us)
422165 UNION-ALL (cr=2865666 r=196521 w=5321 time=192824126 us)
414894 HASH JOIN (cr=262930 r=162381 w=5321 time=137001153 us)
89296 VIEW (cr=126056 r=92188 w=5321 time=125159035 us)
414894 WINDOW SORT (cr=126056 r=92188 w=5321 time=124896635 us)
414894 TABLE ACCESS BY INDEX ROWID WMS_INV_UNITS_TRANS (cr=126056 r=86866 w=0
time=113893622 us)
501253 INDEX RANGE SCAN ZONE_DNMLZ_IDX (cr=1938 r=1937 w=0 time=1775929 us)(object id
38300)
414894 TABLE ACCESS BY INDEX ROWID WMS_INV_UNITS_TRANS (cr=136874 r=70193 w=0
time=10450918 us)
501253 INDEX RANGE SCAN ZONE_DNMLZ_IDX (cr=1938 r=1937 w=0 time=919263 us)(object id
38300)
7271 TABLE ACCESS BY INDEX ROWID WMS_INV_UNITS_TRANS (cr=129034 r=14221 w=0 time=25672120
us)
71150 NESTED LOOPS (cr=66928 r=3816 w=0 time=11055647 us)
7271 VIEW (cr=51644 r=5 w=0 time=2232945 us)
7271 WINDOW SORT (cr=51644 r=5 w=0 time=2220599 us)
7271 TABLE ACCESS BY INDEX ROWID WMS_INV_UNITS_TRANS (cr=51644 r=5 w=0 time=2129560
us)
709147 INDEX RANGE SCAN WMS_INV_UNITS_TRANS_I_TRANS_CO (cr=2417 r=3 w=0 time=491733
us)(object id 37831)
63878 INDEX RANGE SCAN IUT_IU_COMP_IDX (cr=15284 r=3811 w=0 time=8724292 us)(object id
38305)
********************************************************************************
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: SYS
********************************************************************************
OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 2 0.00 0.04 0 0 0 0
Execute 3 0.00 0.00 0 0 0 0
Fetch 10 0.00 197.18 196521 2866079 7 124
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 15 0.00 197.23 196521 2866079 7 124
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 41 0.00 0.00 0 0 0 0
Execute 44 0.00 0.00 0 0 0 0
Fetch 44 0.00 0.02 1 88 0 40
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 129 0.00 0.03 1 88 0 40
Misses in library cache during parse: 2
3 user SQL statements in session.
41 internal SQL statements in session.
44 SQL statements in session.
********************************************************************************
Trace file: gemprod_ora_3469512.trc
Trace file compatibility: 9.00.01
Sort options: default
1 session in tracefile.
3 user SQL statements in trace file.
41 internal SQL statements in trace file.
44 SQL statements in trace file.
5 unique SQL statements in trace file.
516 lines in trace file.
Followup November 9, 2004 - 9am Central time zone:
sorry -- but a multi-page query with tons of unions, inline views, scalar subqueries - etc....
you'll have to look at it and "tune it".
I don't know what your question is (why is the query the way it is)...
I don't know your system
I don't know your schema
I don't know your indexes
You do though, you have the best chance at doing this.
I do get suspicious when I see a UNION with the same table over and over. I'm sure you can do
something about that.
Best way when an IN value can be NULL
November 10, 2004 - 6am Central time zone
Reviewer: A reader
Thanks Tom for a THE most useful site!!!
I must be having a really bad day or ... I have missed something crucial - is there a better way of
evaluating IN values. When I submitt all NULLS I want to get all Employees but when I asign either
of the values I want these to be checked against the employee table.
CREATE OR REPLACE FUNCTION getemployee
(id INTEGER, firstname VARCHAR2, lastname VARCHAR2)
RETURN cursor
AS
empcursor cursor ;
BEGIN
OPEN empcursor FOR
SELECT employee_id FROM employee
WHERE( (id IS NOT NULL AND employee_id = id)
OR id IS NULL
)
AND
( (firstname IS NOT NULL AND first_name = firstname)
OR firstname IS NULL
)
AND
( (lastname IS NOT NULL AND last_name = lastname)
OR lastname IS NULL
);
RETURN empcursor;
END;
Thanks!!!
Followup November 10, 2004 - 7am Central time zone:
for this, you would probably be best off using a ref cursor and native dyanmic sql.
if you send in "nothing" (all nulls) you would like a full scan.
if you send in "id", you would like an index unique scan on that primary key index.
if you send in "lastname", you would like an index range scan on.....
and so on -- meaning, you want different plans for different inputs.
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:1288401763279
Thank you, Thank you, Thank you!!!!!
November 10, 2004 - 8am Central time zone
Reviewer: A reader
Thanks for untieing the Brain Knot (your comment + a couple of coffees ;-)
Thank you! Brilliant as usual!
How about getting rid of the OR's from above
November 10, 2004 - 8am Central time zone
Reviewer: Christoph from Austria
Hi Tom,
this is a different approach to the above query (from "A reader") without beeing concerned about
different execution plans for different column.
create table test(col1 number,col2 number);
alter table test add (constraint pk_test primary key (col1,col2));
insert into test select * from table(create_rows(20)),table(create_rows(1000));
commit;
where create rows is a pipelined function to return the given number of rows
so we inserted 20000 rows
analyze table test compute statistics for table for all indexes for all indexed columns;
var col1 number;
now when we execute
select *
from test
where ((:col1 is not null and col1 = :col1) or (:col1 is null));
we get:
Elapsed: 00:00:00.40
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=15 Card=1048 Bytes=5240)
1 0 TABLE ACCESS (FULL) OF 'TEST' (TABLE) (Cost=15 Card=1048 Bytes=5240)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1406 consistent gets
0 physical reads
0 redo size
326331 bytes sent via SQL*Net to client
15175 bytes received via SQL*Net from client
1335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
20000 rows processed
and for:
select *
from test
where col1 = nvl(:col1,col1);
we get:
20000 rows selected.
Elapsed: 00:00:00.41
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=15 Card=21000 Bytes=105000)
1 0 CONCATENATION
2 1 FILTER
3 2 TABLE ACCESS (FULL) OF 'TEST' (TABLE) (Cost=14 Card=20000 Bytes=100000)
4 1 FILTER
5 4 INDEX (RANGE SCAN) OF 'PK_TEST' (INDEX (UNIQUE)) (Cost=6 Card=1000 Bytes=5000)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1406 consistent gets
0 physical reads
0 redo size
326331 bytes sent via SQL*Net to client
15175 bytes received via SQL*Net from client
1335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
20000 rows processed
ok we did not give :col1 a value so we get the same amount of lio's
but when :col1=1
we get this for the first query
1000 rows selected.
Elapsed: 00:00:00.03
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=15 Card=1048 Bytes=5240)
1 0 TABLE ACCESS (FULL) OF 'TEST' (TABLE) (Cost=15 Card=1048 Bytes=5240)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
143 consistent gets
0 physical reads
0 redo size
16760 bytes sent via SQL*Net to client
1238 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000 rows processed
and this for the second one
1000 rows selected.
Elapsed: 00:00:00.03
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=15 Card=21000 Bytes=105000)
1 0 CONCATENATION
2 1 FILTER
3 2 TABLE ACCESS (FULL) OF 'TEST' (TABLE) (Cost=14 Card=20000 Bytes=100000)
4 1 FILTER
5 4 INDEX (RANGE SCAN) OF 'PK_TEST' (INDEX (UNIQUE)) (Cost=6 Card=1000 Bytes=5000)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
71 consistent gets
0 physical reads
0 redo size
16760 bytes sent via SQL*Net to client
1238 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000 rows processed
so it seems the second query is more efficient if we supply a value, but at least as efficient as
the first one without a value (for lio).
is this assumption right? and also could you please explain the second query plan. i am a little
confused about the full scan of test and the filter concatenation.
thanks a lot
christoph
Need your help
November 15, 2004 - 2pm Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
I have a query using wildcard search, which takes 37 seconds to run:
SQL> connect alias/alias@aliastst
Connected.
SQL> set autotrace traceonly
SQL> set timing on
SQL> SELECT m.tcn, f.fin_agency_id, f.agency_name, m.current_tcn_status, n.nam,
2 n.dob, m.enter_user_id,
3 TO_CHAR (m.enter_date_time, 'MM/DD/YYYY HH24:MI') enter_date_time
4 FROM (SELECT *
5 FROM alias.wq_master
6 WHERE tot IN ('MAP', 'NFUF') AND SOURCE = 'L') m,
7 (SELECT tcn, MAX (DECODE (alias_field, 'NAM', VALUE)) nam,
8 MAX (DECODE (alias_field, 'DOB', VALUE)) dob,
9 MAX (DECODE (alias_field,
10 'REQUESTORID', VALUE
11 )) requestor_id
12 FROM alias.wq_nist
13 WHERE record_number = 0 AND SOURCE = 'N'
14 GROUP BY tcn) n,
15 alias.fin_agency f
16 WHERE m.tcn LIKE 'A104%' AND m.tcn = n.tcn
17 AND n.requestor_id = f.fin_agency_id;
no rows selected
Elapsed: 00:00:37.07
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=38 Card=1 Bytes=122)
1 0 FILTER
2 1 SORT (GROUP BY) (Cost=38 Card=1 Bytes=122)
3 2 MERGE JOIN (CARTESIAN) (Cost=24 Card=1196 Bytes=145912
)
4 3 TABLE ACCESS (BY INDEX ROWID) OF 'WQ_NIST' (Cost=3 C
ard=1 Bytes=33)
5 4 NESTED LOOPS (Cost=5 Card=1 Bytes=81)
6 5 TABLE ACCESS (BY INDEX ROWID) OF 'WQ_MASTER' (Co
st=2 Card=1 Bytes=48)
7 6 INDEX (RANGE SCAN) OF 'PK_WQ_MASTER' (UNIQUE)
(Cost=1 Card=1)
8 5 INDEX (RANGE SCAN) OF 'FK_WQ_NIST_TCN' (NON-UNIQ
UE) (Cost=1 Card=1)
9 3 BUFFER (SORT) (Cost=35 Card=7846 Bytes=321686)
10 9 TABLE ACCESS (FULL) OF 'FIN_AGENCY' (Cost=19 Card=
7846 Bytes=321686)
Statistics
----------------------------------------------------------
0 recursive calls
60 db block gets
370 consistent gets
52451 physical reads
0 redo size
631 bytes sent via SQL*Net to client
369 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
1 sorts (memory)
1 sorts (disk)
0 rows processed
TCN is a VARCHAR2(13) column. All tables involved in this query contain maximum 200 records. If I
change like 'A104%' to like 'A1040000%', the query finish in 1 ~ 2 seconds.
Do you see anything wrong here or how I can improve the query.
Many thanks in advance.
Followup November 15, 2004 - 9pm Central time zone:
what does the tkprof look like
Please help
November 16, 2004 - 9am Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
Here is TKPROF. I still didn't figure out how to get cr=statistics:
TKPROF: Release 9.2.0.5.0 - Production on Tue Nov 16 09:10:42 2004
Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
Trace file: aliastst_ora_5108.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: 80
********************************************************************************
SELECT m.tcn, f.fin_agency_id, f.agency_name, m.current_tcn_status, n.nam,
n.dob, m.enter_user_id,
TO_CHAR (m.enter_date_time, 'MM/DD/YYYY HH24:MI') enter_date_time
FROM (SELECT *
FROM alias.wq_master
WHERE tot IN ('MAP', 'NFUF') AND SOURCE = 'L') m,
(SELECT tcn, MAX (DECODE (alias_field, 'NAM', VALUE)) nam,
MAX (DECODE (alias_field, 'DOB', VALUE)) dob,
MAX (DECODE (alias_field,
'REQUESTORID', VALUE
)) requestor_id
FROM alias.wq_nist
WHERE record_number = 0 AND SOURCE = 'N'
GROUP BY tcn) n,
alias.fin_agency f
WHERE m.tcn LIKE 'A104%' AND m.tcn = n.tcn
AND n.requestor_id = f.fin_agency_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 9.60 36.53 52320 375 60 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 9.62 36.54 52320 375 60 0
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 80
********************************************************************************
OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.00 0 0 0 0
Execute 2 0.00 0.00 0 0 0 0
Fetch 1 9.60 36.53 52320 375 60 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 9.62 36.54 52320 375 60 0
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: aliastst_ora_5108.trc
Trace file compatibility: 9.02.00
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.
51 lines in trace file.
Thanks again.
Followup November 16, 2004 - 12pm Central time zone:
you need to have that cursor closed.
go into sqlplus.
run the query
EXIT sqlplus
and then run tkprof.
Please help
November 16, 2004 - 1pm Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Sorry, here is the TKPROF:
TKPROF: Release 9.2.0.5.0 - Production on Tue Nov 16 13:12:32 2004
Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
Trace file: aliastst_ora_4440.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
Misses in library cache during execute: 1
Optimizer goal: CHOOSE
Parsing user id: 80 (ALIAS)
********************************************************************************
SELECT m.tcn, f.fin_agency_id, f.agency_name, m.current_tcn_status, n.nam,
n.dob, m.enter_user_id,
TO_CHAR (m.enter_date_time, 'MM/DD/YYYY HH24:MI') enter_date_time
FROM (SELECT *
FROM alias.wq_master
WHERE tot IN ('MAP', 'NFUF') AND SOURCE = 'L') m,
(SELECT tcn, MAX (DECODE (alias_field, 'NAM', VALUE)) nam,
MAX (DECODE (alias_field, 'DOB', VALUE)) dob,
MAX (DECODE (alias_field,
'REQUESTORID', VALUE
)) requestor_id
FROM alias.wq_nist
WHERE record_number = 0 AND SOURCE = 'N'
GROUP BY tcn) n,
alias.fin_agency f
WHERE m.tcn LIKE 'A104%' AND m.tcn = n.tcn
AND n.requestor_id = f.fin_agency_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 1 10.01 57.66 51448 397 60 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 10.01 57.66 51448 397 60 0
Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 80 (ALIAS)
Rows Row Source Operation
------- ---------------------------------------------------
0 FILTER
15692 SORT GROUP BY
455068 MERGE JOIN CARTESIAN
58 TABLE ACCESS BY INDEX ROWID OBJ#(41984)
77 NESTED LOOPS
2 TABLE ACCESS BY INDEX ROWID OBJ#(41982)
288 INDEX RANGE SCAN OBJ#(42098) (object id 42098)
74 INDEX RANGE SCAN OBJ#(42488) (object id 42488)
455068 BUFFER SORT
7846 TABLE ACCESS FULL OBJ#(41908)
Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: CHOOSE
0 FILTER
15692 SORT (GROUP BY)
455068 MERGE JOIN (CARTESIAN)
58 TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF
'WQ_NIST'
77 NESTED LOOPS
2 TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF
'WQ_MASTER'
288 INDEX GOAL: ANALYZED (RANGE SCAN) OF
'PK_WQ_MASTER' (UNIQUE)
74 INDEX GOAL: ANALYZED (RANGE SCAN) OF
'FK_WQ_NIST_TCN' (NON-UNIQUE)
455068 BUFFER (SORT)
7846 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'FIN_AGENCY'
********************************************************************************
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 1 10.01 57.66 51448 397 60 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 10.01 57.66 51448 397 60 0
Misses in library cache during parse: 0
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
2 user SQL statements in session.
0 internal SQL statements in session.
2 SQL statements in session.
1 statement EXPLAINed in this session.
********************************************************************************
Trace file: aliastst_ora_4440.trc
Trace file compatibility: 9.02.00
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.
1 SQL statements EXPLAINed using schema:
ALIAS.prof$plan_table
Default table was used.
Table was created.
Table was dropped.
63 lines in trace file.
Followup November 16, 2004 - 10pm Central time zone:
Looking at what it "thought":
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=38 Card=1 Bytes=122)
1 0 FILTER
2 1 SORT (GROUP BY) (Cost=38 Card=1 Bytes=122)
3 2 MERGE JOIN (CARTESIAN) (Cost=24 Card=1196 Bytes=145912)
4 3 TABLE ACCESS (BY INDEX ROWID) OF 'WQ_NIST' (Cost=3 C ard=1 Bytes=33)
5 4 NESTED LOOPS (Cost=5 Card=1 Bytes=81)
6 5 TABLE ACCESS (BY INDEX ROWID) OF 'WQ_MASTER' (Co st=2 Card=1 Bytes=48)
7 6 INDEX (RANGE SCAN) OF 'PK_WQ_MASTER' (UNIQUE) (Cost=1 Card=1)
8 5 INDEX (RANGE SCAN) OF 'FK_WQ_NIST_TCN' (NON-UNIQ UE) (Cost=1 Card=1)
9 3 BUFFER (SORT) (Cost=35 Card=7846 Bytes=321686)
10 9 TABLE ACCESS (FULL) OF 'FIN_AGENCY' (Cost=19 Card= 7846 Bytes=321686)
versus what it "got"
Rows Row Source Operation
------- ---------------------------------------------------
0 FILTER
15692 SORT GROUP BY
455068 MERGE JOIN CARTESIAN
58 TABLE ACCESS BY INDEX ROWID OBJ#(41984)
77 NESTED LOOPS <<<=== way off, it thought "1"
2 TABLE ACCESS BY INDEX ROWID OBJ#(41982)
288 INDEX RANGE SCAN OBJ#(42098) (object id 42098)
74 INDEX RANGE SCAN OBJ#(42488) (object id 42488)
455068 BUFFER SORT
7846 TABLE ACCESS FULL OBJ#(41908)
so, since you know the query and tables and indexes better than I, what part of the query is that
NL join and what predicates are involved and how exactly do you gather statistics (eg: trying to
find out why it was sure "1 row" but got lots more..)
Please help
November 16, 2004 - 2pm Central time zone
Reviewer: Jennifer Chen
Hi Tom,
From the explain plan, the cost seems at full table scan in the fin_agency table and CARTESIAN
MERGE JOIN. f.fin_agency_id is the pk in the fin_agency table, but I have no way to create an index
on n.requestor_id because the way that table was designed...
An analyst at metalink suggests me to apply patch 3444115 to get rid of CARTESIAN MERGE JOIN.
What do you think?
Thanks again for your time and help.
SQL> set lines 130
SQL> SELECT m.tcn, f.fin_agency_id, f.agency_name, m.current_tcn_status, n.nam,
2 n.dob, m.enter_user_id,
3 TO_CHAR (m.enter_date_time, 'MM/DD/YYYY HH24:MI') enter_date_time
4 FROM (SELECT *
5 FROM alias.wq_master
6 WHERE tot IN ('MAP', 'NFUF') AND SOURCE = 'L') m,
7 (SELECT tcn, MAX (DECODE (alias_field, 'NAM', VALUE)) nam,
8 MAX (DECODE (alias_field, 'DOB', VALUE)) dob,
9 MAX (DECODE (alias_field,
10 'REQUESTORID', VALUE
11 )) requestor_id
12 FROM alias.wq_nist
13 WHERE record_number = 0 AND SOURCE = 'N'
14 GROUP BY tcn) n,
15 alias.fin_agency f
16 WHERE m.tcn LIKE 'A104%' AND m.tcn = n.tcn
17 AND n.requestor_id = f.fin_agency_id
18 ;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=37 Card=1 Bytes=122)
1 0 FILTER
2 1 SORT (GROUP BY) (Cost=37 Card=1 Bytes=122)
3 2 MERGE JOIN (CARTESIAN) (Cost=24 Card=1181 Bytes=144082)
4 3 TABLE ACCESS (BY INDEX ROWID) OF 'WQ_NIST' (Cost=3 Card=1 Bytes=33)
5 4 NESTED LOOPS (Cost=5 Card=1 Bytes=81)
6 5 TABLE ACCESS (BY INDEX ROWID) OF 'WQ_MASTER' (Cost=2 Card=1 Bytes=48)
7 6 INDEX (RANGE SCAN) OF 'PK_WQ_MASTER' (UNIQUE)(Cost=1 Card=1)
8 5 INDEX (RANGE SCAN) OF 'FK_WQ_NIST_TCN' (NON-UNIQUE) (Cost=1 Card=1)
9 3 BUFFER (SORT) (Cost=34 Card=7846 Bytes=321686)
10 9 TABLE ACCESS (FULL) OF 'FIN_AGENCY' (Cost=19 Card=7846 Bytes=321686)
Followup November 16, 2004 - 10pm Central time zone:
you could, that virtually disables this access plan (merge join cartesian)
i'd rather try to figure out why the optimizer thinks "1" first - -if it didn't think "1", it would
not cartesian join.
Any Idea how I can re-write this sql
November 16, 2004 - 4pm Central time zone
Reviewer: Mike from nwe york
SELECT x.mbricode, x.ID, x.psdocae, y.cae_nr, y.NAME
FROM (SELECT b.ip_base_nr, a.psdocae, mbricode, ID
FROM member_pseudonym a, stg_match_cae b
WHERE a.mbricode = b.mbr_grp_icode
AND src_ind = 'V'
-- and b.MBR_GRP_ICODE < 50
AND a.psdocae > 0
GROUP BY b.ip_base_nr, a.psdocae, mbricode, ID) x,
(SELECT c.ip_base_nr, c.cae_nr, c.NAME
FROM caematch_ipi c
WHERE EXISTS (SELECT ip_base_nr
FROM stg_match_cae d
WHERE d.ip_base_nr = c.ip_base_nr AND src_ind = 'V')) y
-- and Mbr_grp_icode < 50)) y
WHERE x.ip_base_nr = y.ip_base_nr AND x.psdocae = y.cae_nr
Followup November 16, 2004 - 11pm Central time zone:
not without lots of other details -- not really.
is the relationship from x to y 1:1? scalar subqueries could work.
you are using the CBO right?
Thanks
November 17, 2004 - 9am Central time zone
Reviewer: A reader
Yes I am using CBO and the RElationship is 1:1
Followup November 17, 2004 - 11am Central time zone:
ok, so try things like scalar subqueries and the like (if you don't know what they are -- search
for
"scalar subquery"
"scalar subqueries"
on this site.
A little backwards from the usual
November 24, 2004 - 10am Central time zone
Reviewer: sPh from Saint Louis, Missouri USA
Tom,
Do you have an example of a _bad_ query? What I am thinking of is a fairly simple set of queries,
each building on the last, that starts out running in a reasonable amount of time but at some point
explodes (goes to exponential runtime).
I would like to demonstrate to some of our GUI-tool query users how easy it is to go wrong when one
does not look at the underlying structure of the query. However, while I confess that I have
inadvertently created my share of bad queries, I am not smart enough to do so in a controlled
environment.
Can Tom's brain run in reverse? Inquiring minds want to know!
sPh
Followup November 24, 2004 - 12pm Central time zone:
not sure what you mean -- in general if I have a query q1 that runs "fast", i would expect
select ...
from (Q1)
where ....
to do OK and if we call that Q2, i would expect
select ..
from (Q2)
where ....
to go ok too. so, can you be a little more clear in what you mean exactly -- are you talking about
mega join of views with hidden "features" like group by's, analytics and such?
Tried to create a reply
November 24, 2004 - 12pm Central time zone
Reviewer: sPh from Saint Louis, Missouri USA
I tried to create a more expanded reply, got to about 700 words and realized it is probably not
something suited for this forum. And I probably can't afford your consulting rates.
We have data extractors almost totally trained in the GUI/Microsoft world. They are very resistant
to any suggestion that they need to understand what happens below the GUI level. What I need is
an example I can build up step by step in the GUI tool until it no longer works. Then go back to
Oracle, analyze it using your techniques, and rebuild it as an optimized view.
But creating that would be a non-trivial effort I fear. I have the time but not the ability.
sPh
How to eliminate to query this huge table twice
December 7, 2004 - 3pm Central time zone
Reviewer: Jennifer Chen from Viena, VA USA
Hi Tom,
In the arrest_offense table, one pid, inc, arrest have multiple arrest_offense. I need to return
the smallest arrest_offense and its associated arrest_type and arrest_class. Basically the first
row in the table below. would you please correst my sql so that arrest_type will be in the same row
with arrest_offense and I don't have to query this huge table twice.
Thanks in advance for your help.
pid inc arrest arrest_offense arrest_type arrest_class
1 1 1 1 C XX
1 1 1 2 B YY
1 1 2 1 A ZZ
1 2 3 1 H AA
SELECT pid,
inc,
arrest,
MIN (ao1.arrest),
MIN (arrest_type_code) arrest_type,
MIN (arrest_file_class) arrest_class
FROM alias.arrest_offense ao1
GROUP BY pid, inc, arrest
Followup December 7, 2004 - 8pm Central time zone:
new question and not relevant to the thread started on this page....

January 4, 2005 - 1pm Central time zone
Reviewer: Manish from India
Hi Tom,
I had a very basic question on SQL tuning. Is it true that in case there are complex joins between
tables, we get much better query performance if the primary keys are 'number' instead of
'varchar2'.
thanks for help in advance
Manish
Followup January 5, 2005 - 8am Central time zone:
no, date is much better
kidding.
no, not true. not unless your strings a literraly HUGE -- thousands of bytes.
How to make in SQL statement?
January 7, 2005 - 1pm Central time zone
Reviewer: MARIANA
Hello,Tom,
In PLSQL code i have:
BEGIN
SELECT learnable_prcnt
INTO l_rtrn_rtrn_calc_rtrn_prcnt
FROM
KM_TMP_PREDICT_BRANCH_EDITIONS
WHERE PROD_NO=:prod_no
AND NWPR_DATE=:p_date
AND BRANCH_NO=:p_branch
AND learnable_prcnt>=30;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
BEGIN
SELECT RTRN_PRCNT INTO l_rtrn_rtrn_calc_rtrn_prcnt
FROM KM_TMP_RTRN_STD
WHERE
PROD_NO=:prod_no
AND DAY_IN_WEEK=TO_CHAR(:p_date,'D')
AND BRANCH_NO=:p_branch;
EXCEPTION
WHEN_NO_DATA_FOUND
THEN
l_rtrn_rtrn_calc_rtrn_prcnt=NULL;
END;
END;
How can i make it in one select?
Thank you for answer.
Mariana.
Followup January 8, 2005 - 3pm Central time zone:
how about going way back to the invoker and saying:
instead of you running a query to get umpteen rows and call me over and over, why don't you use
this view:
select .....,
nvl( (select learnable_prcnt from KM_TMP_PREDICT_BRANCH_EDITIONS
where prod_no = X.prod_no and nwp_date = X.wpr_date ..... ),
(select ...... ) )
from your_table X;
that would be one way.
Another way would be:
begin
select nvl( ( SELECT learnable_prcnt
FROM KM_TMP_PREDICT_BRANCH_EDITIONS
WHERE PROD_NO=:prod_no
AND NWPR_DATE=:p_date
AND BRANCH_NO=:p_branch
AND learnable_prcnt>=30 ),
( SELECT RTRN_PRCNT
FROM KM_TMP_RTRN_STD
WHERE PROD_NO=:prod_no
AND DAY_IN_WEEK=TO_CHAR(:p_date,'D')
AND BRANCH_NO=:p_branch ) )
into l_rtrn_rtrn_calc_rtrn_prcnt
from dual;
return l_rtrn_rtrn_calc_rtrn_prcnt;
end;
no exception blocks needed (if you get no_data_found or too_many_rows -- then DUAL itself is
"broken" and that is something you would like to find out)
sure, you could also:
select * from
(SELECT learnable_prcnt, 1
FROM KM_TMP_PREDICT_BRANCH_EDITIONS
WHERE PROD_NO=:prod_no
AND NWPR_DATE=:p_date
AND BRANCH_NO=:p_branch
AND learnable_prcnt>=30
UNION ALL
SELECT RTRN_PRCNT, 2
FROM KM_TMP_RTRN_STD
WHERE PROD_NO=:prod_no
AND DAY_IN_WEEK=TO_CHAR(:p_date,'D')
AND BRANCH_NO=:p_branch
order by 2 )
where rownum = 1
but that might miss a TOO_MANY_ROWS exception that should be thrown when the data is bad.
Continue to make better SQL
January 8, 2005 - 4pm Central time zone
Reviewer: Mariana
Hello,Tom,
Thank you for a great solution to better SQL:
begin
select nvl( ( SELECT learnable_prcnt
FROM KM_TMP_PREDICT_BRANCH_EDITIONS
WHERE PROD_NO=:prod_no
AND NWPR_DATE=:p_date
AND BRANCH_NO=:p_branch
AND learnable_prcnt>=30 ),
( SELECT RTRN_PRCNT
FROM KM_TMP_RTRN_STD
WHERE PROD_NO=:prod_no
AND DAY_IN_WEEK=TO_CHAR(:p_date,'D')
AND BRANCH_NO=:p_branch ) )
into l_rtrn_rtrn_calc_rtrn_prcnt
from dual;
return l_rtrn_rtrn_calc_rtrn_prcnt;
end;
If at this query no data fount or too many rows or something else then will the field
l_rtrn_rtrn_calc_rtrn_prcnt be NULL?
Why in SQL from dual does'nt have exceptions?
Mariana
Followup January 8, 2005 - 5pm Central time zone:
that query can return
scott@ORA9IR2> select ( select 1 from all_users ) from dual;
select ( select 1 from all_users ) from dual
*
ERROR at line 1:
ORA-01427: single-row subquery returns more than one row
but will never throw no data found:
scott@ORA9IR2> select ( select 1 from all_users where 1=0 ) from dual;
(SELECT1FROMALL_USERSWHERE1=0)
------------------------------
scott@ORA9IR2>
for the simple reason that DUAL always has a row -- it will always find "data". and
scalarsubqueries are allowed to return "no data", it is considered "NULL"
Cont to SQL tunning
January 8, 2005 - 5pm Central time zone
Reviewer: A reader
Hi,Tom,
you wrote above:
begin
select nvl( ( SELECT learnable_prcnt
FROM KM_TMP_PREDICT_BRANCH_EDITIONS
WHERE PROD_NO=:prod_no
AND NWPR_DATE=:p_date
AND BRANCH_NO=:p_branch
AND learnable_prcnt>=30 ),
( SELECT RTRN_PRCNT
FROM KM_TMP_RTRN_STD
WHERE PROD_NO=:prod_no
AND DAY_IN_WEEK=TO_CHAR(:p_date,'D')
AND BRANCH_NO=:p_branch ) )
into l_rtrn_rtrn_calc_rtrn_prcnt
from dual;
return l_rtrn_rtrn_calc_rtrn_prcnt;
end;
no exception blocks needed (if you get no_data_found or too_many_rows -- then
DUAL itself is "broken" and that is something you would like to find out)
and latter you gave the example
with too many rows in scalar subquery from dual
Is'nt it a contradiction?
Does this Sql work in ORacle 8i?
When you wrote "return l_rtrn_rtrn_calc_rtrn_prcnt"
you meant that this SQL IS WRITTEN IN FUNCTION?
Thank you
Followup January 8, 2005 - 5pm Central time zone:
Not a contradiction
well written code catches exceptions it expects to recieve.
No more, no less.
Do you "expect" in any reasonable set of circumstances for that query to return too_many_rows? I
cannot fathom the case whereby that would be true. In order for that to hit TOO_MANY_ROWS -- dual
would have to have MORE THAN ONE row.
You *cannot* tell me you were "expecting that"
therefore, that MUST be propagated up the call stack as an unhandled exception!
There will be NO ONE in the call stack that was "expecting" dual to return more than one row --
therefore the CLIENT will get this error and the client can abort processing -- since there is an
exception deep down in the database that no one EXPECTED
Bad code, that would have a too_many rows or a "when others" and would perhaps silently ignore this
truly EXCEPTIONAL condition -- causing bugs in other places all over the place
You catch exceptions YOU EXPECT.
If you didn't expect it, you better not catch it, or if you do, you BETTER follow it with a RAISE
to re-raise it. You hit an error that *should not have happened, you have no idea how to deal with
it, all bets off -- time to call it a day"
I find people handle errors so "incorrectly". the goal is NOT to catch them all and hide them.
The goal rather is to catch things YOU EXPECT (no_data_found for example, in the original code) and
do something about it.
the goal IS NOT to catch them all and "dbms_output" some message and carry on as if they did not
happen!!!!!
In 8i, the scalar subquery was not supported in PLSQL witout dynamic sql or a view to "hide" the
construct. same with order by in a subquery/inline view, that'll not work either.
Cont to sql tuning in ORACLE 8.1.7
January 8, 2005 - 6pm Central time zone
Reviewer: A reader
Hi,Tom,
In ORACLE 8I
this solution would be the best?
select * from
(SELECT learnable_prcnt, 1
FROM KM_TMP_PREDICT_BRANCH_EDITIONS
WHERE PROD_NO=:prod_no
AND NWPR_DATE=:p_date
AND BRANCH_NO=:p_branch
AND learnable_prcnt>=30
UNION ALL
SELECT RTRN_PRCNT, 2
FROM KM_TMP_RTRN_STD
WHERE PROD_NO=:prod_no
AND DAY_IN_WEEK=TO_CHAR(:p_date,'D')
AND BRANCH_NO=:p_branch
order by 2 )
where rownum = 1
What do you mean in saying ?
"but that might miss a TOO_MANY_ROWS exception that should be thrown when the
data is bad"
Thank you very much.
Mariana
Followup January 8, 2005 - 7pm Central time zone:
you've got an order by in a subquery/inline view
In any release, the best would be to remove the function alltogether and just select it out in the
view I described above!
(and think about what what too_many_rows means and how this could miss it and how that would mean
you don't detect integrity issues the original approach you used would find immediately......)
Order in subquery in ORACLE 8I
January 9, 2005 - 8am Central time zone
Reviewer: A reader
Hello,Tom,
I invoked with order by in a subquery:
select * into....
(select * from t_dates order by n_date desc) where rownum<=4
in PL/SQL code -Oracle8i in TOAD and it worked.
You wrote above that
In 8i, order by in a subquery/inline view,
will not work in 8i.
I am confused.
Thank you.
Followup January 9, 2005 - 11am Central time zone:
guess I was thinking about this:
tkyte@ORA8IW> begin
2 insert into t
3 select 1 from dual order by 1;
4 end;
5 /
select 1 from dual order by 1;
*
ERROR at line 3:
ORA-06550: line 3, column 23:
PLS-00103: Encountered the symbol "ORDER" when expecting one of the following:
. , @ ; return returning <an identifier>
<a double-quoted delimited-identifier> group having intersect
minus partition start subpartition union where connect
SAMPLE_
The symbol "group was inserted before "ORDER" to continue.
tkyte@ORA8IW> insert into t
2 select 1 from dual order by 1;
1 row created.
Cont of sql tunning
January 9, 2005 - 2pm Central time zone
Reviewer: Mriana
Hello,Tom,
Why only in inserts: insert into t
select 1 from dual order by 1;
the subquery order is not supported in Oracle 8i?
Why anoteher type with subquery with order is supported in Oracle 8i?
Thank you very much.
Mariana.
Followup January 9, 2005 - 4pm Central time zone:
don't know. just is.
Continue to make a better sql
January 9, 2005 - 5pm Central time zone
Reviewer: A reader
Hi,Tom,
You wrote above that the solution to define view and to make select from the view :
select .....,
nvl( (select learnable_prcnt from KM_TMP_PREDICT_BRANCH_EDITIONS
where prod_no = X.prod_no and nwp_date = X.wpr_date ..... ),
(select ...... ) )
from your_table X;
But x.prod_no and x.nwpr_date are the variables in my PL/SQL code,or may be i didn't understand?
Thank you
MARIANA
Followup January 9, 2005 - 6pm Central time zone:
they are from "your_table X"
they are correlation variables
I was saying "instead of someone running a query:
for A in ( select * from your_table where .... )
loop
call_your_proceudre( a.column1, a.column2 )
they could just run that query
for A in ( select * from view where ... )
loop
-- no need to call your function, already GOT it.
For example:
ops$tkyte@ORA9IR2> create table t1 ( x int, y int, z int );
Table created.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create table t2 ( x int, y int, a varchar2(10) );
Table created.
ops$tkyte@ORA9IR2> create table t3 ( x int, y int, b varchar2(10) );
Table created.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t1 select rownum, rownum, rownum from all_users;
24 rows created.
ops$tkyte@ORA9IR2> insert into t2 select x, y, 't2 ' || z from t1 where mod(x,2)=0;
12 rows created.
ops$tkyte@ORA9IR2> insert into t3 select x, y, 't3 ' || z from t1 where mod(x,2)=1;
12 rows created.
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace view v
2 as
3 select X.x, X.y, X.z,
4 nvl( (select a from t2 where t2.x = X.x and t2.y = X.y),
5 (select b from t3 where t3.x = X.x and t3.y = X.y) ) data
6 from t1 X
7 /
View created.
coder will use that view, instead of this function:
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace
2 procedure p( p_x in number, p_y in number, p_data out varchar2 )
3 as
4 begin
5 select DATA
6 into p_data
7 from ( select a data, 1
8 from t2
9 where t2.x = p_x
10 and t2.y = p_y
11 union all
12 select b data, 1
13 from t3
14 where t3.x = p_x
15 and t3.y = p_y
16 order by 2 )
17 where rownum = 1;
18 end;
19 /
Procedure created.
so their code simply becomes:
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> begin
2 for c in ( select * from v where x in ( 4, 5 ) )
3 loop
4 dbms_output.put_line( c.x || ', ' || c.y || ', ' || c.data );
5 end loop;
6 end;
7 /
4, 4, t2 4
5, 5, t3 5
PL/SQL procedure successfully completed.
instead of:
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
2 l_data varchar2(10);
3 begin
4 for c in ( select * from t1 where x in ( 4, 5 ) )
5 loop
6 p( c.x, c.y, l_data );
7 dbms_output.put_line( c.x || ', ' || c.y || ', ' || l_data );
8 end loop;
9 end;
10 /
4, 4, t2 4
5, 5, t3 5
PL/SQL procedure successfully completed.
query tuning
January 10, 2005 - 6am Central time zone
Reviewer: P.Karthick from India
hai tom,
today i was writing a query. after compleating the query i was checking the index of the tables for
query tuning. the query returns sum 100000 odd records. it was taking few seconds. then i found out
that i dint select a specific column which i wanted to retrive but have created the join with the
table. so i included that column also in the select statement. to my surprise i found that the
query returned the data just like that (i mean it was prity fast)
eg:
my first query
select a.x,a.y from a,b where a.z = b.z
my corrected query
select a.x,a.y,b.m from a,b where a.z=b.z
the second one was faster..
so can you please tell whether this two query makes any difference in performance..
iam using oracle 9i.
Thank you.
Followup January 10, 2005 - 8am Central time zone:
you gotta compare plans.
compare the before and after plans.
mismatch datatype
January 11, 2005 - 4pm Central time zone
Reviewer: Danny Chen from cerritos,ca
Tom,
create table t1 (col number(2));
Is there any different between the SQL below:
insert into t1 values('4');
insert into t1 values(4);
will it affect the performance of a SQL later?
Followup January 11, 2005 - 4pm Central time zone:
the second one is correct, the first one is not correct.
you have a number, not a string, give numbers. you'll have the overhead of an implicit
conversion.
(and make sure you use bind variables in real life)
Decode Vs. Union
January 12, 2005 - 4am Central time zone
Reviewer: Ganesh from Bangalore, India
Tom,
Please suggest a better query for the below.
SQL> CREATE TABLE emp (eno NUMBER(3), ename VARCHAR2(30), join_date DATE, resign_date DATE)
2 /
Table created.
SQL> INSERT INTO emp
2 VALUES (1, 'A', '01-JAN-2005', NULL)
3 /
1 row created.
SQL> INSERT INTO emp
2 VALUES (2, 'B', '01-JAN-2005', '09-JAN-2005')
3 /
1 row created.
SQL> INSERT INTO emp
2 VALUES (3, 'C', '01-JAN-2005', '30-JAN-2005')
3 /
1 row created.
SQL> COMMIT
2 /
Commit complete.
SQL> SELECT eno,
2 ename,
3 'Active'
4 FROM emp
5 WHERE SYSDATE BETWEEN join_date AND NVL (resign_date, SYSDATE)
6 UNION
7 SELECT eno,
8 ename,
9 'Not Active'
10 FROM emp
11 WHERE SYSDATE NOT BETWEEN join_date AND NVL (resign_date, SYSDATE)
12 /
ENO ENAME 'ACTIVE'
---------- ------------------------------ ----------
1 A Active
2 B Not Active
3 C Active
SQL> SELECT eno,
2 ename,
3 DECODE ( DECODE (SIGN (SYSDATE - join_date), 1, 1, 0)
4 + DECODE (SIGN (NVL (resign_date, SYSDATE + 1) - SYSDATE), 1, 1, 0),
5 2, 'Active',
6 'Not Active'
7 ) "Active"
8 FROM emp
9 /
ENO ENAME Active
---------- ------------------------------ ----------
1 A Active
2 B Not Active
3 C Active
SQL>
Regards,
Ganesh
Followup January 12, 2005 - 8am Central time zone:
you did the work for me. what's wrong with your last very simple query.
you could use CASE of course to make it more readable
case when .... then ....
when ... then....
else ...
end
Query Tunning
January 17, 2005 - 1am Central time zone
Reviewer: Muhammad Riaz Shahid from PRAL, Lahore, Pakistan
Hello Tom !
Consider the example:
create table t(ac_no number(9), fis_dt date, bud_amt1 number(10,2),bud_amt2 number(10,2), bud_amt3
number(10,2));
insert into t values(123,sysdate, 100,200,300);
insert into t values(456,sysdate, 900,300,400);
commit;
Here is the query:
select ac_no, add_months(fis_dt,0), bud_amt1 from t
union all
select ac_no, add_months(fis_dt,1), bud_amt2 from t
union all
select ac_no, add_months(fis_dt,2), bud_amt3 from t
/
It causes 3 FTS to T....i thought we can optimize this one using some decode(max(....)) function.
Can you give me a clue ?
Followup January 17, 2005 - 8am Central time zone:
if you need three rows, instead of a single row/ac_no with 3 pairs of values -- you either have to
a) union all as you are doing
b) cartesian product this to a 3 row table to output each row 3 times
c) use a pipelined function
you have A), you can search this site for pipelined to see "how to c" and here is b:
ops$tkyte@ORA9IR2> select ac_no, add_months(fis_dt,r) fis_dt,
decode(r,0,bud_amt1,1,bud_amt2,bud_amt3) bud_amt
2 from t,
3 (select 0 r from dual union all select 1 from dual union all select 2 from dual )
4 /
AC_NO FIS_DT BUD_AMT
---------- --------- ----------
123 17-JAN-05 100
456 17-JAN-05 900
123 17-FEB-05 200
456 17-FEB-05 300
123 17-MAR-05 300
456 17-MAR-05 400
6 rows selected.
sort for cartesian join (?)
January 17, 2005 - 10am Central time zone
Reviewer: Alberto Dell'Era from Milan, Italy
> b) cartesian product this to a 3 row table to output each row 3 times
It may get a bit expensive since it seems that the table gets ordered in memory:
create table t (x) as select rownum from all_objects where rownum <= 10000;
exec dbms_stats.gather_table_stats (user,'t');
explain plan for
select t.x
from t, (select 1 r from dual union all select 2 from dual);
select * from table (dbms_xplan.display);
In 9.2.0.6:
--------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
--------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 163M| 467M| 65366 |
| 1 | MERGE JOIN CARTESIAN| | 163M| 467M| 65366 |
| 2 | VIEW | | 16336 | | 22 |
| 3 | UNION-ALL | | | | |
| 4 | TABLE ACCESS FULL| DUAL | 8168 | | 11 |
| 5 | TABLE ACCESS FULL| DUAL | 8168 | | 11 |
| 6 | BUFFER SORT | | 10000 | 30000 | 65366 |
| 7 | TABLE ACCESS FULL | T | 10000 | 30000 | 4 |
--------------------------------------------------------------------
In 10.1.0.3:
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20000 | 60000 | 14 (0)| 00:00:01 |
| 1 | MERGE JOIN CARTESIAN| | 20000 | 60000 | 14 (0)| 00:00:01 |
| 2 | VIEW | | 2 | | 4 (0)| 00:00:01 |
| 3 | UNION-ALL | | | | | |
| 4 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 5 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 6 | BUFFER SORT | | 10000 | 30000 | 14 (0)| 00:00:01 |
| 7 | TABLE ACCESS FULL | T | 10000 | 30000 | 5 (0)| 00:00:01 |
-----------------------------------------------------------------------------
Better in 10g since the cardinality of dual is right,
hence the cardinality of the result set is right too (20,000),
which may be critical if the result set had to be further processed.
But the plan is the same, and it implies a buffer sort of T;
why sorting and then merge joining, especially since the CBO
understands that it is a cartesian product (it says "MERGE JOIN CARTESIAN") ?
Or is this perhaps a "fake" sort, an artifacts of "explain plan" ?
(btw i have dumped the plans from v$sql_plan and they are the same).
Followup January 17, 2005 - 11am Central time zone:
it is not being sorted, since there is no key for the join.
there is no sort in this case, there is a buffer (since it knows I'll have to access the same row
over and over)

January 17, 2005 - 11am Central time zone
Reviewer: Alberto Dell'Era from Milan, Italy
>there is no sort in this case, there is a buffer (since it knows I'll have to
>access the same row over and over)
So in essence it uses the "merge join engine", but disabling the sorting on the two joined sets -
and the disabling of the sorts doesn't get reflected on the displayed plan (should read "BUFFER
NOSORT", in a manner) ?
Thanks!
Followup January 17, 2005 - 11am Central time zone:
yes
Tune the query
February 11, 2005 - 12pm Central time zone
Reviewer: A reader
Tom,
i have the query ran for more than one hour and used 52 millions logical reads.
the trace shows the index range scan used 1,605,385,980 rows. the table pa_reources is only 6110
rows.
apperently it does too much un-necessary work. please advise on tune the query.
The following are the table sizes:
PA_PROJECTS_ALL
22371
PA_resources
6110
PA_TASKS
723577
P2PA_EXPENDITURES_IMPORT_STG
362270
SELECT
UPPER(P.PROJECT_TYPE) PTYPE,
'PRJ_'||UPPER(S.PROJECT_NUMBER) PROJECT,
'PRJ_'||UPPER(S.PROJECT_NUMBER)||'_TSK_'||UPPER(T.TASK_NUMBER) ACTIVITY,
'EROC_'||UPPER(S.CEFMS_FOA_CODE) EROC,
'ORG_'||UPPER(S.COST_ORG_CODE) RES,
'APS_'||S.APPROP_DEPT_CODE||'_'||S.APPROP_SYMBOL APPROP,
DECODE(SUBSTR(S.ACCOUNT_PERIOD,5,2),
'01','JAN_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'02','FEB_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'03','MAR_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'04','APR_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'05','MAY_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'06','JUN_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'07','JUL_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'08','AUG_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'09','SEP_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'10','OCT_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'11','NOV_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'12','DEC_'||SUBSTR(S.ACCOUNT_PERIOD,3,2)) PERIOD,
SUM(S.PTD_BURDEN_COST+S.PTD_RAW_COST) AMOUNT,
SUM(S.PTD_QUANTITY) HOURS
FROM P2PA_EXPENDITURES_IMPORT_STG S,
APPS.PA_PROJECTS_ALL P,
APPS.PA_TASKS T,
APPS.PA_PROJECT_STATUSES S2,
APPS.PA_RESOURCES R
WHERE P.PROJECT_ID = S.PROJECT_ID
AND P.PROJECT_ID = T.PROJECT_ID
AND P.PROJECT_STATUS_CODE = S2.PROJECT_STATUS_CODE
AND ( SUBSTR(R.NAME,1,7)=S.COST_ORG_CODE or SUBSTR(R.NAME,1,6)=S.COST_ORG_CODE )
AND T.TASK_ID = S.TASK_ID
AND R.RESOURCE_TYPE_ID = '103'
AND T.TASK_ID NOT IN (SELECT DISTINCT Z.PARENT_TASK_ID FROM APPS.PA_TASKS Z WHERE
Z.PARENT_TASK_ID IS NOT NULL)
AND P.TEMPLATE_FLAG <> 'Y'
AND P.PM_PROJECT_REFERENCE IS NOT NULL
AND T.TASK_NUMBER <> '1'
AND T.TASK_NUMBER <> '1.0'
AND UPPER(S2.PROJECT_STATUS_NAME) <> 'INVALID'
AND UPPER(S2.PROJECT_STATUS_NAME) <> 'UNAPPROVED - FUTURE'
AND S.TRANSACTION_SOURCE = 'CEFMS_LABOR'
GROUP BY
UPPER(P.PROJECT_TYPE),
'PRJ_'||UPPER(S.PROJECT_NUMBER),
'PRJ_'||UPPER(S.PROJECT_NUMBER)||'_TSK_'||UPPER(T.TASK_NUMBER),
'EROC_'||UPPER(S.CEFMS_FOA_CODE),
'ORG_'||UPPER(S.COST_ORG_CODE),
'APS_'||S.APPROP_DEPT_CODE||'_'||S.APPROP_SYMBOL,
DECODE(SUBSTR(S.ACCOUNT_PERIOD,5,2),
'01','JAN_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'02','FEB_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'03','MAR_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'04','APR_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'05','MAY_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'06','JUN_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'07','JUL_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'08','AUG_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'09','SEP_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'10','OCT_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'11','NOV_'||SUBSTR(S.ACCOUNT_PERIOD,3,2),
'12','DEC_'||SUBSTR(S.ACCOUNT_PERIOD,3,2))
HAVING SUM(S.PTD_BURDEN_COST+S.PTD_RAW_COST) <> 0 OR SUM(S.PTD_QUANTITY) <> 0
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 13468 4843.79 5116.61 33625 52066249 62 134665
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 13470 4843.88 5116.70 33625 52066249 62 134665
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 173 (APPS)
Rows Row Source Operation
------- ---------------------------------------------------
134665 FILTER
134869 SORT GROUP BY
270413 FILTER
270414 NESTED LOOPS
270268 HASH JOIN
709993 HASH JOIN
20768 HASH JOIN
36 TABLE ACCESS FULL PA_PROJECT_STATUSES
21430 TABLE ACCESS FULL PA_PROJECTS_ALL
717742 TABLE ACCESS FULL PA_TASKS
270554 TABLE ACCESS FULL P2PA_EXPENDITURES_IMPORT_STG
540680 TABLE ACCESS BY INDEX ROWID PA_RESOURCES
1605385980 INDEX RANGE SCAN (object id 41614)
46373 INDEX RANGE SCAN (object id 41561)
Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: CHOOSE
134665 FILTER
134869 SORT (GROUP BY)
270413 FILTER
270414 NESTED LOOPS
270268 HASH JOIN
709993 HASH JOIN
20768 HASH JOIN
36 TABLE ACCESS GOAL: ANALYZED (FULL) OF
'PA_PROJECT_STATUSES'
21430 TABLE ACCESS GOAL: ANALYZED (FULL) OF
'PA_PROJECTS_ALL'
717742 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'PA_TASKS'
270554 TABLE ACCESS GOAL: ANALYZED (FULL) OF
'P2PA_EXPENDITURES_IMPORT_STG'
540680 TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF
'PA_RESOURCES'
1605385980 INDEX GOAL: ANALYZED (RANGE SCAN) OF
'PA_RESOURCES_N1' (NON-UNIQUE)
46373 INDEX GOAL: ANALYZED (RANGE SCAN) OF 'PA_TASKS_N4'
(NON-UNIQUE)
********************************************************************************

March 9, 2005 - 11am Central time zone
Reviewer: A reader
IMHO
The optimizer is incorrectly choosing Index with Nested Loop Join for PA_RESOURCES table where as
FTS/Hash join might be better. Probably stats are not good and histograms might help in this case
You can even consider running the query in parallel, if you have sufficient memory and CPU
You can even consider increasing the arraysize
to say some 100 or 200
Sql Tuning
March 15, 2005 - 3am Central time zone
Reviewer: Spawn from Phils
Hi Tom,
I know your time is precious but could you please help me with this one. Just some advice on what I
can do. Either reconstructing the sql or something. Thanks.
select rev_rese_no, rev_reit_no, count(*) from nx_reservation_items
WHERE trunc(create_date)||'1' > TO_DATE('30-JUN-2004','DD-MON-RRRR')||'1'
having count(*) > 1
GROUP BY rev_rese_no, rev_reit_no
REV_RESE_NO REV_REIT_NO COUNT(*)
----------- ----------- ----------
2476526
Elapsed: 00:05:25.11
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=56383 Card=33352 Byt
es=400224)
1 0 FILTER
2 1 SORT (GROUP BY) (Cost=56383 Card=33352 Bytes=400224)
3 2 TABLE ACCESS (FULL) OF 'NX_RESERVATION_ITEMS' (Cost=55
249 Card=667034 Bytes=8004408)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
562097 consistent gets
503708 physical reads
104 redo size
502 bytes sent via SQL*Net to client
504 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
Below are the indexes containing the create_date column and it's position in that index.
INDEX_NAME TABLE_NAME COLUMN_NAME COLUMN_POSITION
REIT_TD_REP NX_RESERVATION_ITEMS CREATE_DATE 4
TICKET_DEL_SUMM_REP NX_RESERVATION_ITEMS CREATE_DATE 5
Followup March 15, 2005 - 8am Central time zone:
select rev_rese_no, rev_reit_no, count(*)
from nx_reservation_items
WHERE create_date > TO_DATE('30-JUN-2004','DD-MON-RRRR')
GROUP BY rev_rese_no, rev_reit_no
having count(*) > 1
One might ask "whats up with the trunc, ||'1' stuff"
what was the point of that?
if the sole goal is to make this "faster", an index on
(create_date,rev_rese_no, rev_reit_no)
would be called for.
sometime index is used
March 24, 2005 - 9am Central time zone
Reviewer: riyaz from india
drop table t1;
drop table t2;
create table t1 (comp_code varchar2(3), control_no number(5),
loc_code varchar2(3));
alter table t1 add constraints pk_t1 primary key(comp_code, control_no);
create table t2 (comp_code varchar2(3), comp_desc varchar2(10));
alter table t2 add constraints pk_t2 primary key(comp_code);
insert into t1(select 'a',rownum,rownum from user_objects);
update t1 set comp_code='b' where rownum<5;
insert into t2 values ('a','aaaa');
insert into t2 values ('b','aaaa');
insert into t2 values ('c','aaaa');
Wrote file afiedt.buf
1 select t1.comp_code, t1.loc_code, t2.comp_desc
2 from t1,t2
3 where t1.comp_code = t2.comp_code
4* and t1.loc_code=1
> /
COM LOC COMP_DESC
--- --- ----------
b 1 bbb
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=5 Bytes=110)
1 0 NESTED LOOPS (Cost=2 Card=5 Bytes=110)
2 1 TABLE ACCESS (FULL) OF 'T2' (Cost=1 Card=1 Bytes=10)
3 1 TABLE ACCESS (FULL) OF 'T1' (Cost=1 Card=5 Bytes=60)
Statistics
----------------------------------------------------------
0 recursive calls
16 db block gets
15 consistent gets
0 physical reads
0 redo size
953 bytes sent via SQL*Net to client
762 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
> ed
Wrote file afiedt.buf
1 select t1.comp_code, t1.loc_code, t2.comp_desc
2 from t1,t2
3 where t1.comp_code = t2.comp_code
4* and t1.loc_code='a'
5 /
no rows selected
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=22)
1 0 NESTED LOOPS (Cost=2 Card=1 Bytes=22)
2 1 TABLE ACCESS (FULL) OF 'T1' (Cost=1 Card=1 Bytes=12)
3 1 TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=1 Card=1 Bytes=10)
4 3 INDEX (UNIQUE SCAN) OF 'PK_T2' (UNIQUE)
Statistics
----------------------------------------------------------
0 recursive calls
4 db block gets
4 consistent gets
0 physical reads
0 redo size
831 bytes sent via SQL*Net to client
653 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
0 rows processed
Here my question is why full scan has been done when where condition is t1.loc_code=1 (returns
rows)
and using index scan when t1.loc_code='a' (which returns no rows)
My point is actually the index to be used if comp_code is there in where condition (filter),but it
is not there in either of the sql. But still it is used in sql with where condition loc_code='a'
Acutally in my production, i am facing problem which is similar to the above. I have simulated with
examples of t1,t2 table here. Please answer.
Followup March 24, 2005 - 3pm Central time zone:
do you see that you are comparing string to number?
does that not concern you?
look at the output from dbms_xplan and you'll see precisely why, look at what your implicit
conversions do to the predicates:
ops$tkyte@ORA10GR1> @plan "select t1.comp_code, t1.loc_code, t2.comp_desc from t1,t2 where
t1.comp_code = t2.comp_code and t1.loc_code=1"
ops$tkyte@ORA10GR1> delete from plan_table;
5 rows deleted.
ops$tkyte@ORA10GR1> explain plan for &1;
old 1: explain plan for &1
new 1: explain plan for select t1.comp_code, t1.loc_code, t2.comp_desc from t1,t2 where
t1.comp_code = t2.comp_code and t1.loc_code=1
Explained.
ops$tkyte@ORA10GR1> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
---------------------
Plan hash value: 498178312
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 16 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 16 | 4 (0)| 00:00:01 |
|* 2 | TABLE ACCESS FULL | T1 | 1 | 6 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 10 | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | PK_T2 | 1 | | 0 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(TO_NUMBER("T1"."LOC_CODE")=1) <<<<<==== to_number(COLUMN)
4 - access("T1"."COMP_CODE"="T2"."COMP_CODE")
Note
-----
- dynamic sampling used for this statement
21 rows selected.
ops$tkyte@ORA10GR1>
ops$tkyte@ORA10GR1> @plan "select t1.comp_code, t1.loc_code, t2.comp_desc from t1,t2 where
t1.comp_code = t2.comp_code and t1.loc_code='1'"
ops$tkyte@ORA10GR1> delete from plan_table;
5 rows deleted.
ops$tkyte@ORA10GR1> explain plan for &1;
old 1: explain plan for &1
new 1: explain plan for select t1.comp_code, t1.loc_code, t2.comp_desc from t1,t2 where
t1.comp_code = t2.comp_code and t1.loc_code='1'
Explained.
ops$tkyte@ORA10GR1> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
---------------------
Plan hash value: 498178312
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 16 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 16 | 4 (0)| 00:00:01 |
|* 2 | TABLE ACCESS FULL | T1 | 1 | 6 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 10 | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | PK_T2 | 1 | | 0 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("T1"."LOC_CODE"='1') <<<<<===== no conversion (
4 - access("T1"."COMP_CODE"="T2"."COMP_CODE")
Note
-----
- dynamic sampling used for this statement
21 rows selected.
compare numbers to numbers
strings to strings
dates to dates
PERIOD
great explanation
March 25, 2005 - 12am Central time zone
Reviewer: riyaz from india
very sorry, When I simulated, I did it wrong. Once started tuning explain plan, it is really
interesting and challanging also. (I have your book, effective design by oracle)
Let me give you the production sql itself and explain.
Actually production sql is:
@ORACLE> select distinct control_trace.comp_code comp_code, receive_location loc_code,
2 comp_mas.comp_desc comp_desc
3 from control_trace, comp_mas
4 where control_trace.comp_code = comp_mas.comp_code
5 and control_trace.move_type ='R'
6 and receive_location ='CHU';
I am trying trace for the following:
@ORACLE> select distinct control_trace.comp_code comp_code, receive_location loc_code,
2 comp_mas.comp_desc comp_desc
3 from control_trace, comp_mas
4 where control_trace.comp_code = comp_mas.comp_code
5 and control_trace.move_type ='R'
6 and receive_location ='CHU';
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=28 Card=1407 Bytes=139293)
1 0 HASH JOIN (Cost=28 Card=1407 Bytes=139293)
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'CONTROL_TRACE' (Cost=2 Card=39 Bytes=1365)
3 2 INDEX (RANGE SCAN) OF 'IND_CTRACE_RECLOC' (NON-UNIQUE) (Cost=1 Card=39)
4 1 TABLE ACCESS (FULL) OF 'COMP_MAS' (Cost=25 Card=3608 Bytes=230912)
Statistics
----------------------------------------------------------
0 recursive calls
4 db block gets
527 consistent gets
235 physical reads
0 redo size
3195 bytes sent via SQL*Net to client
1219 bytes received via SQL*Net from client
7 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
51 rows processed
Explanation:
comp_mas has primary key for comp code
control_trace has primary key (comp_code + control_no)
and now I created the index for control_trace.receive_location.
After the index creation, optimizer uses the index of control_trace.receive location.
('IND_CTRACE_RECLOC' )
But it is not using the index of comp_mas.comp_code - why?
Before i created ind_ctrace_recloc, both the tables control_trace and comp_mas was in FULL scan.
After creating receive location index, control_trace is using index. But still comp_mas is not
using comp_mas(comp_code) - please explain. (I am fully experimenting explain plan with this,
so please answer)
Additional information.
control trace - 307152 records
comp mas - 3608 records
control_trace for location CHU - 274 (the above sql)
the above sql matching records (both table) - 51 records
the abvoe sql with distinct - 4 records
And also tell me, how to rewrite the sql without distinct.
Followup March 25, 2005 - 7am Central time zone:
why should it use an index?
go ahead and hint it to use an index and compare the work performed by each using tkprof
the abvove trace output
March 25, 2005 - 12am Central time zone
Reviewer: riyaz from india
the above trace output is WITHOUT distinct.
yes. full scan is faster
March 25, 2005 - 9am Central time zone
Reviewer: riyaz from india
Yes. it takes more time. Now I clearly understood. Thanks a lot.
select /*+ RULE */ control_trace.comp_code comp_code, receive_location loc_code,
comp_mas.comp_desc comp_desc
from control_trace, comp_mas
where control_trace.comp_code = comp_mas.comp_code
and control_trace.move_type ='R'
and receive_location ='CHU';
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=HINT: RULE
1 0 NESTED LOOPS
2 1 TABLE ACCESS (FULL) OF 'COMP_MAS'
3 1 TABLE ACCESS (BY INDEX ROWID) OF 'CONTROL_TRACE'
4 3 INDEX (RANGE SCAN) OF 'PK_CONTROL_TRACE' (UNIQUE)
Statistics
----------------------------------------------------------
0 recursive calls
4 db block gets
56739 consistent gets
0 physical reads
0 redo size
2224 bytes sent via SQL*Net to client
1037 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
24 rows processed
Followup March 25, 2005 - 6pm Central time zone:
just keep saying to youself
1) "indexes are not all goodness"
2) "fulls scans are not evil incarnate"
3) goto 1)
Not Using Primary key index
April 7, 2005 - 2pm Central time zone
Reviewer: Arya from Reston, VA
Hi Tom,
When I executed the following query and looked at the plan to my surprise it is doing full table
scan on contact_persons and not using primary key index on contact_persons table. when I take out
full_name from column list and use cp_id then it is using primary key index. full_name is not a
indexed column.
So my question is why is it not using primary key index ?Is this new behaviour or was it like this
all the time?
SQL> select cp.cp_id, cp.full_name
from cp, projects p
where p.star_project_owner_cp_id = cp.cp_id
and p.updated_by = 'harish.donthi@gsa.gov';
65 rows selected.
Elapsed: 00:00:00.03
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=374 Card=3461 Bytes=124596)
1 0 HASH JOIN (Cost=374 Card=3461 Bytes=124596)
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'PROJECTS' (Cost=45 Card=3461 Bytes=58837)
3 2 INDEX (RANGE SCAN) OF 'PROJ_UPDATED_BY_I' (NON-UNIQUE)(Cost=2 Card=3685)
4 1 TABLE ACCESS (FULL) OF 'CONTACT_PERSONS' (Cost=317 Card=218114 Bytes=4144166)
Statistics
----------------------------------------------------------
14 recursive calls
0 db block gets
2153 consistent gets
4 physical reads
0 redo size
2258 bytes sent via SQL*Net to client
547 bytes received via SQL*Net from client
6 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
65 rows processed
If I create concatenated index with cp_id, full_name then it is doing primary key index scan
instead of full table scan.
Thanks
Arya
Followup April 7, 2005 - 4pm Central time zone:
show me.
show me the other plans.
and remember
1) full scans are not evil
2) indexes are not all goodness
3) goto 1 until you believe it
it seems that the optimizer is saying "I don't want to do 3,461 index range scans (that would be 2
or 3 LIO's against the index for each of the 3,461 rows it things it will get) followed by a table
access by index rowid.
the real question is why does it think 3,461 rows when there are only 65. how are stats gathered?
are they accurate/upto date? is updated_by very skewed?
Not using Primary key index
April 7, 2005 - 6pm Central time zone
Reviewer: Arya from Reston, VA
Hi Tom,
I'm good follower of this site and I read many times you saying that full table scans are not evil
and I do agree with that but my question is If I am joining with primary key and fetching only 65
rows then why is it going for full table scan. I have added concatenated index on cp_id and
full_name columns of contact_persons table and ran the query again. Here are the results:
SQL> set autotrace traceonly
SQL> select cp.cp_id, cp.full_name
2 from cp, projects p
3 where p.star_project_owner_cp_id = cp.cp_id
4 and p.updated_by = 'harish.donthi@gsa.gov';
65 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=205 Card=3461 Bytes=
124596)
1 0 HASH JOIN (Cost=205 Card=3461 Bytes=124596)
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'PROJECTS' (Cost=45 Car
d=3461 Bytes=58837)
3 2 INDEX (RANGE SCAN) OF 'PROJ_UPDATED_BY_I' (NON-UNIQUE)
(Cost=2 Card=3685)
4 1 INDEX (FAST FULL SCAN) OF 'CP_PK_FULL_NAME_I' (NON-UNIQU
E) (Cost=148 Card=218114 Bytes=4144166)
Statistics
----------------------------------------------------------
14 recursive calls
0 db block gets
1051 consistent gets
0 physical reads
0 redo size
2257 bytes sent via SQL*Net to client
547 bytes received via SQL*Net from client
6 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
65 rows processed
It is using index scan so I have dropped and run again.
SQL> drop index cp_pk_full_name_i;
Index dropped.
SQL> select cp.cp_id, cp.full_name
2 from cp, projects p
3 where p.star_project_owner_cp_id = cp.cp_id
4 and p.updated_by = 'harish.donthi@gsa.gov';
65 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=374 Card=3461 Bytes=
124596)
1 0 HASH JOIN (Cost=374 Card=3461 Bytes=124596)
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'PROJECTS' (Cost=45 Car
d=3461 Bytes=58837)
3 2 INDEX (RANGE SCAN) OF 'PROJ_UPDATED_BY_I' (NON-UNIQUE)
(Cost=2 Card=3685)
4 1 TABLE ACCESS (FULL) OF 'CONTACT_PERSONS' (Cost=317 Card=
218114 Bytes=4144166)
Statistics
----------------------------------------------------------
37 recursive calls
0 db block gets
2161 consistent gets
1769 physical reads
0 redo size
2258 bytes sent via SQL*Net to client
547 bytes received via SQL*Net from client
6 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
65 rows processed
SQL>
Again it goes for full table scan with out using primary key index. All the statistics are up to
date. If I'm joining based on primary key then it should be using primary key am I wrong?
Thanks
Arya
Followup April 7, 2005 - 6pm Central time zone:
did you see the autotrace -- I tried to allude to that above.
Card=3461
the optimizer things 3,461 -- not 65
it was using an INDEX FAST FULL SCAN, which is simply using the index as if it were a skinnier
version of the table is all.
the optimizer is saying "no way I'm going to goto the table 3,461 times using 3 LIO's through the
index and one more for the table, full scan"
So, why does it think 3,461 when there are apparently only 65. compare a tkprof (actual rowcounts)
vs the autotrace and see where they diverge.
Primary key is not used in query
April 8, 2005 - 3pm Central time zone
Reviewer: arya from Reston,VA
Hi Tom,
I'll run the query with tkprof and get back to you if I find any clue.
Thanks
arya
Not using Primary key index
April 8, 2005 - 6pm Central time zone
Reviewer: Arya from Reston, VA
Hi Tom,
I ran the query with sql_trace on but with concatenated primary key and here is the tkprof output
for your review.
I request you to help me point out where is the problem.
TKPROF: Release 9.2.0.1.0 - Production on Fri Apr 8 18:22:26 2005
Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
Trace file: devdb_ora_2156.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
Misses in library cache during execute: 1
Optimizer goal: CHOOSE
Parsing user id: 119 (ELEASER1)
********************************************************************************
select cp.cp_id, cp.full_name
from cp, projects p
where p.star_project_owner_cp_id = cp.cp_id
and p.updated_by = :"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 6 0.00 0.25 115 1046 0 63
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 8 0.00 0.26 115 1046 0 63
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 119 (ELEASER1)
Rows Row Source Operation
------- ---------------------------------------------------
63 HASH JOIN
63 TABLE ACCESS BY INDEX ROWID PROJECTS
63 INDEX RANGE SCAN PROJ_UPDATED_BY_I (object id 61394)
218114 INDEX FAST FULL SCAN CP_PK_FULL_NAME_I (object id 61399)
Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: CHOOSE
63 HASH JOIN
63 TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'PROJECTS'
63 INDEX GOAL: ANALYZED (RANGE SCAN) OF 'PROJ_UPDATED_BY_I'
(NON-UNIQUE)
218114 INDEX GOAL: ANALYZED (FAST FULL SCAN) OF 'CP_PK_FULL_NAME_I'
(NON-UNIQUE)
********************************************************************************
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: 119 (ELEASER1)
********************************************************************************
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.00 0 0 0 0
Fetch 6 0.00 0.25 115 1046 0 63
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 11 0.00 0.26 115 1046 0 63
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 3 0.00 0.00 0 0 0 0
Execute 3 0.00 0.00 0 0 0 0
Fetch 3 0.00 0.00 0 7 0 3
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 9 0.00 0.00 0 7 0 3
Misses in library cache during parse: 2
3 user SQL statements in session.
3 internal SQL statements in session.
6 SQL statements in session.
1 statement EXPLAINed in this session.
********************************************************************************
Trace file: devdb_ora_2156.trc
Trace file compatibility: 9.00.01
Sort options: default
1 session in tracefile.
3 user SQL statements in trace file.
3 internal SQL statements in trace file.
6 SQL statements in trace file.
5 unique SQL statements in trace file.
1 SQL statements EXPLAINed using schema:
ELEASER1.prof$plan_table
Default table was used.
Table was created.
Table was dropped.
75 lines in trace file.
Thanking you,
Arya
Followup April 9, 2005 - 7am Central time zone:
why cursoring sharnig = similar|force is on is my first question?
Good point
April 15, 2005 - 12pm Central time zone
Reviewer: Arya from Reston, VA
Hi Tom,
Thanks for pointing out about cursor_sharing parameter since our application is j2ee my dba has put
this parameter to force to enable binding I talked to him showed him all your material regarding
this parameter and he has changed it to exact, still query plan shows full table scan on
contact_persons table without using primary key. My guess is as first step of query it has to fetch
70,000 matching records from projects table it is doing full table scan am I right?
I have another question one of the developer has written a query with 11 table outer join for a
report and I don't know how to improve performance of this query, I have looked at explain plan and
it seems to be ok so can you outline some ideas regarding this query? tried to create materialized
view with on commit but it has a restriction to have unique constraints on the columns invovled in
joins of inner tables.
I will appreciate your invaluable help in this regard.
Thanks
Arya
Followup April 15, 2005 - 1pm Central time zone:
since your application is j2ee doesn't mean cursor sharing should be on. it means you should be
binding when appropriate in the code. google for
sql injection
you've got it big time I'd bet.
but we've been down this path. Do you see the estimated cardinalities there?
it is thinking thousands, it gets 63.
first_rows hint it I guess in this case.
what index is required for this query
April 18, 2005 - 7am Central time zone
Reviewer: sreenivasa rao from INDIA
Dear TOM,
MY production database running on 9.2.0.1 version and is having table like this
SQL> SQL> desc dedup_precook_data
Name Null? Type
----------------------------------------- -------- ----------------------------
DPD_SR_NO NOT NULL NUMBER
DPD_TABLE_ID NOT NULL VARCHAR2(1)
DPD_ROWID NOT NULL ROWID
DPD_UPLOADID NUMBER
DPD_STATUS VARCHAR2(1)
DPD_LOGIC1 NUMBER(25)
DPD_LOGIC2_1 NUMBER(25)
DPD_LOGIC2_2 NUMBER(25)
DPD_LOGIC3_1 NUMBER(25)
DPD_LOGIC3_2 NUMBER(25)
DPD_LOGIC4_1 NUMBER(25)
DPD_LOGIC4_2 NUMBER(25)
DPD_LOGIC5_1 NUMBER(25)
DPD_LOGIC5_2 NUMBER(25)
DPD_LOGIC6_1 NUMBER(25)
DPD_LOGIC6_2 NUMBER(25)
DPD_LOGIC6_3 NUMBER(25)
DPD_LOGIC6_4 NUMBER(25)
DPD_LOGIC7 NUMBER(25)
DPD_LOGIC8 NUMBER(38)
DPD_LOGIC9_1 NUMBER(25)
DPD_LOGIC9_2 NUMBER(25)
DPD_LOGIC10_1 NUMBER(25)
DPD_LOGIC10_2 NUMBER(25)
DPD_LOGIC11 NUMBER(25)
DPD_LOGIC12 NUMBER(25)
DPD_LOGIC13 NUMBER(25)
DPD_LOGIC14_1 NUMBER(38)
DPD_LOGIC14_2 NUMBER(38)
DPD_LOGIC15 NUMBER(25)
DPD_LOGIC16 NUMBER(25)
DPD_LOGIC18_1 NUMBER(25)
DPD_LOGIC18_2 NUMBER(25)
DPD_LOGIC18_3 NUMBER(25)
DPD_LOGIC7E NUMBER(25)
DPD_LOGIC7F NUMBER(25)
DPD_LOGIC7G NUMBER(25)
DPD_LOGIC20 NUMBER(25)
DPD_LOGIC20_E NUMBER(25)
DPD_LOGIC20_F NUMBER(25)
DPD_LOGIC20_G NUMBER(25)
DPD_LOGIC20_H NUMBER(25)
DPD_LOGIC20_I NUMBER(25)
DPD_LOGIC21 NUMBER(25)
DPD_LOGIC22 NUMBER(25)
DPD_LOGIC23 NUMBER(25)
DPD_LOGIC24 NUMBER(25)
DPD_LOGIC25 NUMBER(25)
Query running inside a job is
INSERT INTO gtt_dedup_result
(drm_sys_id, drm_id_num, drm_comp_appl_id, drm_customerid, drm_table_id,
drm_rowid, drm_uploadid)
(SELECT seq_dedup_result.NEXTVAL, NULL, :b60, :b59, dpd_table_id, dpd_rowid,
dpd_uploadid
FROM (SELECT dp.dpd_table_id dpd_table_id, dp.dpd_rowid dpd_rowid,
dpd_uploadid
FROM dedup_precook_data dp
WHERE (:b58 = dp.dpd_logic1
OR :b57 = dp.dpd_logic1
OR :b56 = dp.dpd_logic2_1
OR :b55 = dp.dpd_logic2_2
OR :b56 = dp.dpd_logic2_2
OR :b55 = dp.dpd_logic2_1
OR :b54 = dp.dpd_logic3_1
AND (:b50 = dp.dpd_logic5_1
OR :b49 = dp.dpd_logic5_1
OR :b50 = dp.dpd_logic5_2
OR :b49 = dp.dpd_logic5_2)
OR :b51 = dp.dpd_logic3_2
AND (:b53 = dp.dpd_logic5_1
OR :b52 = dp.dpd_logic5_1
OR :b53 = dp.dpd_logic5_2
OR :b52 = dp.dpd_logic5_2)
OR :b54 = dp.dpd_logic3_1
AND (:b53 = dp.dpd_logic5_1
OR :b52 = dp.dpd_logic5_1
OR :b53 = dp.dpd_logic5_2
OR :b52 = dp.dpd_logic5_2)
OR :b51 = dp.dpd_logic3_2
AND (:b50 = dp.dpd_logic5_1
OR :b49 = dp.dpd_logic5_1
OR :b50 = dp.dpd_logic5_2
OR :b49 = dp.dpd_logic5_2)
OR :b54 = dp.dpd_logic3_2
AND (:b50 = dp.dpd_logic5_1
OR :b49 = dp.dpd_logic5_1
OR :b50 = dp.dpd_logic5_2
OR :b49 = dp.dpd_logic5_2)
OR :b51 = dp.dpd_logic3_1
AND (:b53 = dp.dpd_logic5_1
OR :b52 = dp.dpd_logic5_1
OR :b53 = dp.dpd_logic5_2
OR :b52 = dp.dpd_logic5_2)
OR :b54 = dp.dpd_logic3_2
AND (:b53 = dp.dpd_logic5_1
OR :b52 = dp.dpd_logic5_1
OR :b53 = dp.dpd_logic5_2
OR :b52 = dp.dpd_logic5_2)
OR :b51 = dp.dpd_logic3_1
AND (:b50 = dp.dpd_logic5_1
OR :b49 = dp.dpd_logic5_1
OR :b50 = dp.dpd_logic5_2
OR :b49 = dp.dpd_logic5_2)
OR :b48 = dp.dpd_logic4_1
OR :b47 = dp.dpd_logic4_1
OR :b46 = dp.dpd_logic4_1
OR :b45 = dp.dpd_logic4_1
OR :b44 = dp.dpd_logic4_2
OR :b43 = dp.dpd_logic4_2
OR :b42 = dp.dpd_logic4_2
OR :b41 = dp.dpd_logic4_2
OR :b40 = dp.dpd_logic6_1
OR :b40 = dp.dpd_logic6_3
OR :b39 = dp.dpd_logic6_1
OR :b39 = dp.dpd_logic6_3
OR :b38 = dp.dpd_logic6_1
OR :b38 = dp.dpd_logic6_3
OR :b37 = dp.dpd_logic6_1
OR :b37 = dp.dpd_logic6_3
OR :b36 = dp.dpd_logic7
OR :b35 = dp.dpd_logic7
OR :b34 = dp.dpd_logic20
OR :b33 = dp.dpd_logic8
OR :b32 = dp.dpd_logic8
OR :b31 = dp.dpd_logic9_1
OR :b30 = dp.dpd_logic9_1
OR :b29 = dp.dpd_logic9_2
OR :b28 = dp.dpd_logic9_2
OR :b27 = dp.dpd_logic10_1
OR :b26 = dp.dpd_logic10_1
OR :b25 = dp.dpd_logic10_2
OR :b24 = dp.dpd_logic10_2
OR :b23 = dp.dpd_logic15
OR :b22 = dp.dpd_logic15
OR :b21 = dp.dpd_logic18_1
OR :b20 = dp.dpd_logic18_2
OR :b19 = dp.dpd_logic18_3
OR :b18 = dp.dpd_logic21
OR :b17 = dp.dpd_logic21
OR :b16 = dp.dpd_logic21
OR :b15 = dp.dpd_logic21
OR :b14 = dp.dpd_logic21
OR :b13 = dp.dpd_logic21
OR :b18 = dp.dpd_logic22
OR :b17 = dp.dpd_logic22
OR :b16 = dp.dpd_logic22
OR :b15 = dp.dpd_logic22
OR :b14 = dp.dpd_logic22
OR :b13 = dp.dpd_logic22
OR :b18 = dp.dpd_logic23
OR :b17 = dp.dpd_logic23
OR :b16 = dp.dpd_logic23
OR :b15 = dp.dpd_logic23
OR :b14 = dp.dpd_logic23
OR :b13 = dp.dpd_logic23
OR :b12 = dp.dpd_logic7e
OR :b11 = dp.dpd_logic7e
OR :b10 = dp.dpd_logic7e
OR :b9 = dp.dpd_logic7e
OR :b8 = dp.dpd_logic7e
OR :b7 = dp.dpd_logic7e
OR :b6 = dp.dpd_logic7e
OR :b5 = dp.dpd_logic7e
OR :b4 = dp.dpd_logic7e
OR :b3 = dp.dpd_logic7e
OR :b2 = dp.dpd_logic7e
OR :b1 = dp.dpd_logic7e)
AND dp.dpd_status = 'A'))
explain plan
3 INSERT STATEMENT
2 . SEQUENCE
1 SCB.DEDUP_PRECOOK_DATA TABLE ACCESS [FULL
here dedup_precook_data table size is 4Gb.
Kindly advise me,how to tune this query?
is there any index recommonded?
regards,
sreenivas
Followup April 18, 2005 - 7am Central time zone:
wow.
anyway, the only thing that would pop to mind would be single column bitmap indexes on each of
dpd_logic7e, dpd_logic_22, ..... (the columns you and/or together)
single column, not concatenated.
and only if this 4gig table is read mostly.
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:32633204077527
see that page and read/understand the 3 pointed to articles before you issue a create bitmap index
statement in your database.
thanks for the blistering speed of your site and response.
April 18, 2005 - 9am Central time zone
Reviewer: A reader
Tom,really your the best
Re: what index is required for this query
April 18, 2005 - 11am Central time zone
Reviewer: Gabe
Long shot
but if dp.dpd_status = 'A' is _very_ selective then an index on dpd_status may suffice.
<quote>Query running inside a job is
</quote>
If the job is infrequent then maybe the binds are a bit of overkill.
Followup April 18, 2005 - 11am Central time zone:
good eye, I didn't match up parenthesis that spanned pages :)
single column bitmap indexes
April 19, 2005 - 12am Central time zone
Reviewer: sreenivasa rao from INDIA
Dear Tom,
Once again thanks for the above clarification.
could you clarify these doubts.
1a. if star_trnsformation_enabled is set to true,does all the foreign key columns need single
column bitmap indexes or concatenated index on all foreign key columns
1b. would Btree indexes work for the same.
could you clarify this concept with your commanding skills.
2.i have a database with 9.2.0 having table like this.
one primary key.
10 foreign keys.
5 general columns
would it be good to enable star_transformation on the quries having these type of tables(PK,lot of
FKs, and some general columns).
as you expect this table having lot of joins with it's child tables in quries..(here i am specific
about general keys also.)
regards
sreenivas
Any thoughts on this query it seems to be taking a long time
April 27, 2005 - 10am Central time zone
Reviewer: Juan from Mexico
select WORK_GROUP, WORK_GROUP_DESCRIPTION, SECTION_CODE, SECTION_DESCRIPTION, QUEUE_CODE,
QUEUE_DESCRIPTION, ALLOCATED_MACHINES, AVAILABLE_MACHINE_NUM, MOVES,
ORDERS_PER_MACHINES, UNSLOTTED_ORDERS, UNSLOTTED_TRUCKS, AVAILABLE_SLOTS,
PROBLEM_MOVES ) AS SELECT work_group,
work_group_description,
section_code,
section_description,
queue_code,
queue_description,
allocated_machines,
available_machine_num,
(moves - PROBLEM_MOVES) MOVES,
orders_per_machines,
unslotted_orders,
unslotted_trucks,
(CASE WHEN unslotted_orders = 0 OR available_slots < 0 THEN 0
ELSE NVL(available_slots ,0) END) available_slots
,PROBLEM_MOVES
FROM
(
SELECT wq.*
,NVL(m.Allocated_machines,0) Allocated_machines
,NVL(m.Available_machine_num,0) Available_machine_num
,NVL(ord.moves,0) MOVES
,NVL2(m.Available_machine_num,NVL(ord.moves,0)-NVL(PROBLEM_MOVES ,0),0)/ (CASE WHEN
m.Available_machine_num = 0 OR m.Available_machine_num IS NULL THEN 1
ELSE m.Available_machine_num
END) Orders_per_machines
,NVL(ord.unslotted_orders,0) unslotted_orders
,NVL(ord.unslotted_trucks,0) unslotted_trucks
, (select slots#
from(
select s.code,COUNT(*) slots#
from yard_blocks b,sections s, sam_rows r
where b.block_type ='L'
and b.yard_id = (select yard_id from yards where yard_id = b.yard_id
and active =1)
and s.slotline = b.block_name
AND r.rowno <= b.number_of_rows
AND yard_pkg.isLocationBlockedOut_FUNC(b.block_name,r.rowno) = 'N'
GROUP BY s.code
)
where code = wq.section_code) - ord.slotted_orders Available_slots
,NVL(PROBLEM_MOVES ,0) PROBLEM_MOVES
FROM
(SELECT GQ.WORK_GROUP_CODE Work_group
,G.Description Work_group_description
,s.code section_code
,s.description SECTION_DESCRIPTION
,GQ.WORK_QUEUE_CODE QUEUE_CODE
,q.description QUEUE_DESCRIPTION
FROM work_group_queues gq,Work_groups G, Work_queues Q, (select code,description FROM sections
union all SELECT 'ALL', 'ALL SECTIONS' FROM DUAL) s
WHERE gq.work_group_code = g.code
AND gq.work_queue_code = q.code
) wq, (SELECT distinct NVL(section_code,'ALL') SECTION_CODE,queue_code
,COUNT(machine_id) over (partition by NVL(section_code,'ALL'), queue_code)
Allocated_machines
,SUM(CASE WHEN (mo.mode_code ='ON' OR Mo.mode_code = 'NIS') AND mo.driver_username IS
NOT NULL AND mo.all_sections = 'Y'
THEN 1
WHEN (mo.mode_code ='ON' OR Mo.mode_code = 'NIS') AND mo.driver_username IS
NOT NULL AND mo.all_sections = 'N'
THEN 1
ELSE NULL
END) Over (partition by NVL(section_code,'ALL'), queue_code)
Available_machine_num
FROM machine_orders mo) m,
((SELECT *
FROM(
SELECT DISTINCT w.section_code,
CASE WHEN w.queue_code = 'TRUCK' AND w.slotted_date IS NOT NULL THEN 'TRUCK'
WHEN w.queue_code = 'TRUCK' AND w.slotted_date IS NULL THEN null
ELSE w.queue_code
END queue_code
, count(*) over (partition by w.section_code,
CASE
WHEN w.queue_code = 'TRUCK' AND
w.slotted_date IS NOT NULL
THEN 'TRUCK'
WHEN w.queue_code = 'TRUCK' AND
w.slotted_date IS NULL
THEN NULL
ELSE w.queue_code
END) MOVES
, SUM(CASE WHEN w.queue_code = 'TRUCK' THEN NVL2(w.slotted_date,0,1) ELSE NULL END)
over (partition by w.section_code, w.queue_code) Unslotted_orders
, SUM(CASE WHEN w.queue_code = 'TRUCK' THEN NVL2(w.slotted_date,1,0) ELSE NULL END)
over (partition by w.section_code, w.queue_code) slotted_orders
, SUM(CASE WHEN w.queue_code = 'TRUCK' THEN NVL2(w.slotted_date,0,NVL((select 1 FROM
gate_containers gc
where
gc.visit = w.visit
and gc.reference_id = w.visit_ref_id
and gc.voided_date is NULL
),0)
)
ELSE NULL END) OVER (PARTITION BY w.section_code,w.queue_code)
UNSLOTTED_TRUCKS
, SUM(CASE WHEN w.order_problem = 'Y'
OR w.hold = 'Y'
OR w.status_code = 'I' AND w.move_type = 'D'
AND w.reefer = 1 AND w.plug_unplug = 'P'
THEN 1
ELSE 0
END) OVER (PARTITION BY w.section_code,w.queue_code)PROBLEM_MOVES
FROM
(SELECT wo.order_id,wo.queue_code,wo.slotted_date,
wo.assigned_date,wo.visit,wo.visit_ref_id,wo.to_block,wo.inv_container_id
, (CASE WHEN wo.queue_code = 'TRUCK' AND wo.slotted_date IS NULL
THEN (SELECT gc.section_code FROM gate_containers gc WHERE gc.visit =
wo.visit AND gc.reference_id = wo.visit_ref_id)
WHEN wo.queue_code = 'TRUCK' AND wo.slotted_date IS NOT NULL
THEN get_section_func( case when wo.move_type ='R' OR wo.move_type =
'Y'
then ic.L1
when wo.move_type = 'D' then
wo.to_block
else null
end
)
ELSE NVL2( wo.machine_id
, (Select NVL(section_code,'ALL') from machine_orders mo
where mo.machine_id = wo.machine_id)
, get_section_func (
CASE WHEN
yard_pkg.get_block_area_FUNC(ic.L1) = 'RAIL '
THEN wo.to_block
ELSE ic.L1
END
)
)
END) SECTION_CODE
,wo.hold
,wo.order_problem
,wo.move_type
,ic.reefer
,ic.status_code
,ic.plug_unplug
FROM work_orders wo, inv_containers ic
WHERE wo.inv_container_id = ic.container_id(+)
) w
) WHERE queue_code IS NOT NULL
)
UNION ALL
(SELECT DISTINCT 'ALL',mo.queue_code,NULL, NULL,NULL,null,NULL
FROM machine_orders mo
WHERE
mo.all_sections = 'Y'
AND NOT EXISTS (SELECT 'x' FROM work_orders wo
WHERE wo.machine_id = mo.machine_id
OR wo.queue_code = mo.queue_code
)
)
UNION ALL
(SELECT DISTINCT mo.section_code,mo.queue_code,NULL, NULL,NULL,NULL,NULL
FROM machine_orders mo
WHERE
mo.all_sections = 'N'
AND NOT EXISTS (SELECT 'x' FROM work_orders wo,inv_containers ic
WHERE
wo.inv_container_id = ic.container_id(+)
AND
(CASE WHEN wo.queue_code = 'TRUCK' AND wo.slotted_date IS NULL
THEN (SELECT gc.section_code FROM gate_containers gc WHERE
gc.visit = wo.visit AND gc.reference_id = wo.visit_ref_id)
WHEN wo.queue_code = 'TRUCK' AND wo.slotted_date IS NOT NULL
THEN
get_section_func( case when wo.move_type ='R' OR
wo.move_type = 'Y'
then ic.L1
when wo.move_type = 'D' then
wo.to_block
else null
end
)
ELSE get_section_func (
CASE WHEN
yard_pkg.get_block_area_FUNC(ic.L1) = 'RAIL'
THEN wo.to_block
ELSE ic.L1
END
)
END) = mo.section_code
AND wo.queue_code = mo.queue_code
)
)
) ord
where wq.section_code = m.section_code(+)
AND wq.queue_code =m.queue_code(+)
AND wq.section_code = ord.section_code
AND wq.queue_code = ord.queue_code
)
Followup April 27, 2005 - 10am Central time zone:
it is really "long" :) sorry -- too big to sort of look at here.
things like:
get_section_func (
CASE WHEN
yard_pkg.get_block_area_FUNC(ic.L1) = 'RAIL'
THEN
wo.to_block
ELSE ic.L1
END
)
END) = mo.section_code
won't be "fast" (calling lots of plsql from sql). I'd be looking to remove them.
How can I write in the SQL as suppose to call it?
April 27, 2005 - 12pm Central time zone
Reviewer: Juan
FUNCTION isActivatedBlock_FUNC(block_name_in yard_blocks.block_name%TYPE)
RETURN BOOLEAN
IS
TYPE actblk_cur_ref IS REF CURSOR;
actblk_cur actblk_cur_ref;
v_blk_exist VARCHAR2(1) := NULL;
BEGIN
OPEN actblk_cur FOR
'SELECT ''x''
FROM yards y, yard_blocks yb
WHERE y.yard_id = yb.yard_id
AND y.active = 1
AND yb.block_name = :1'
USING block_name_in;
FETCH actblk_cur INTO v_blk_exist;
CLOSE actblk_cur;
IF v_blk_exist IS NOT NULL THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
EXCEPTION
WHEN OTHERS THEN
File_Pkg.log_alert(YARDMSG,'isActivatedBlock_FUNC -- '||TO_CHAR(SQLCODE)||SQLERRM);
RETURN FALSE;
Followup April 27, 2005 - 12pm Central time zone:
man oh man, the dreaded "when others" that doesn't let the caller know "hey, something horrible has
happened". Instead it says "lets trick that guy into thinking everything is OKEY DOKEY"......
dynamic sql when none is called for....
in sql, you can
select ....,
nvl( (select 'Y' from yards y, yard_blocks yb
where ... yb.block_name = OUTER_QUERY.BLOCK_NAME), 'N' ) isactive,
....
from (whatever) OUTER_QUERY;
just use a scalar subquery. if need be, add "and rownum=1" to the scalar subquery.
query issues
May 9, 2005 - 10am Central time zone
Reviewer: whizkid from APAC
Tom, we have this query which gets executed around 40 times in half an hour window (from statspack
report).
SELECT (-1) * SUM(A.POSTING_AMT) FROM ACC_POSTINGS A , AC_INTERNAL_ACCOUNTS C
WHERE A.INTERNAL_ACCOUNT_ID = C.INTERNAL_ACCOUNT_ID
AND C.SUBFACTOR_1_VAL = :B2 AND C.ACCOUNT_CATEGORY_CODE = '2111200001'
AND A.EFFECTIVE_DATE <= :B1 HAVING SUM(POSTING_AMT) < 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.92 28.03 5219 6099 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.92 28.04 5219 6099 0 1
Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS
Rows Row Source Operation
------- ---------------------------------------------------
1 FILTER
1 SORT AGGREGATE
3355 TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(66995) PARTITION: ROW LOCATION ROW LOCATION
3357 NESTED LOOPS
1 TABLE ACCESS BY INDEX ROWID OBJ#(48196)
30881 INDEX RANGE SCAN OBJ#(48199) (object id 48199)
3355 PARTITION RANGE ITERATOR PARTITION: KEY KEY
3355 INDEX RANGE SCAN OBJ#(67385) PARTITION: KEY KEY (object id 67385)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 2 0.00 0.00
db file sequential read 5219 0.11 26.88
global cache cr request 2671 0.00 0.63
SQL*Net message from client 2 5.12 5.12
SQL> select table_name, num_rows from dba_tables where table_name in
('ACC_POSTINGS','AC_INTERNAL_ACCOUNTS');
TABLE_NAME NUM_ROWS
------------------------------ ----------
AC_INTERNAL_ACCOUNTS 12975806
ACC_POSTINGS 30870700
The ACC_POSTINGS table is partitioned on batch_id column. It is not being used in this query but it
is in lots of other queries. The AC_INTERNAL_ACCOUNTS table is not partitioned. The explain plan
could not be any better
SQL> explain plan for
2 SELECT (-1) * SUM(A.POSTING_AMT) FROM ACC_POSTINGS A , AC_INTERNAL_ACCOUNTS C
3 WHERE A.INTERNAL_ACCOUNT_ID = C.INTERNAL_ACCOUNT_ID
4 AND C.SUBFACTOR_1_VAL = :B2 AND C.ACCOUNT_CATEGORY_CODE = '2111200001'
5 AND A.EFFECTIVE_DATE <= :B1 HAVING SUM(POSTING_AMT) < 0
6 /
Explained.
Elapsed: 00:00:00.10
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
----------
| Id | Operation | Name | Rows | Bytes | Cost |
Pstart| Pstop |
----------------------------------------------------------------------------------------------------
----------
| 0 | SELECT STATEMENT | | 1 | 44 | 8 |
| |
|* 1 | FILTER | | | | |
| |
| 2 | SORT AGGREGATE | | 1 | 44 | |
| |
|* 3 | TABLE ACCESS BY GLOBAL INDEX ROWID| ACC_POSTINGS | 1 | 19 | 3 |
ROWID | ROW L |
| 4 | NESTED LOOPS | | 1 | 44 | 8 |
| |
|* 5 | TABLE ACCESS BY INDEX ROWID | AC_INTERNAL_ACCOUNTS | 1 | 25 | 5 |
| |
|* 6 | INDEX RANGE SCAN | IND_ACINTACC_SF1VAL | 3 | | 3 |
| |
| 7 | PARTITION RANGE ITERATOR | | | | |
KEY | KEY |
|* 8 | INDEX RANGE SCAN | ACC_P_IX_1 | 2 | | 2 |
KEY | KEY |
----------------------------------------------------------------------------------------------------
----------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(SUM("A"."POSTING_AMT")<0)
3 - filter("A"."EFFECTIVE_DATE"<=:Z)
5 - filter("C"."ACCOUNT_CATEGORY_CODE"='2111200001')
6 - access("C"."SUBFACTOR_1_VAL"=:Z)
8 - access("A"."INTERNAL_ACCOUNT_ID"="C"."INTERNAL_ACCOUNT_ID")
Note: cpu costing is off
25 rows selected.
It's waiting on sequential read. Is there anything that I could do to reduce the wait time? What
about partitioning the other table on INTERNAL_ACCOUNT_ID and a global index on that column? I have
done that in test env but it has not made any improvement. (well it's the same enviroment)You
comments will be really appreciated. This is a Oracle 9.2.0.5 2 node RAC on IBM AIX 5.2
Followup May 9, 2005 - 10am Central time zone:
well, the explain plan might *LOOK* ok, but the tkprof shows a real problem:
Rows Row Source Operation
------- ---------------------------------------------------
1 FILTER
1 SORT AGGREGATE
3355 TABLE ACCESS BY GLOBAL INDEX ROWID OBJ#(66995) PARTITION: ROW .....
3357 NESTED LOOPS
1 TABLE ACCESS BY INDEX ROWID OBJ#(48196)
30881 INDEX RANGE SCAN OBJ#(48199) (object id 48199)
3355 PARTITION RANGE ITERATOR PARTITION: KEY KEY
3355 INDEX RANGE SCAN OBJ#(67385) PARTITION: KEY KEY (object id 67385)
we hit the table 30,881 times -- to find a SINGLE ROW!
I don't know, but I might rather have:
C.INTERNAL_ACCOUNT_ID
AND C.SUBFACTOR_1_VAL = :B2 AND C.ACCOUNT_CATEGORY_CODE = '2111200001'
those three columns in a concatenated index -- at a glance. I'd want to avoid having to hit the
table 10's of thousands of times needlessly.
cont. of above
May 9, 2005 - 10am Central time zone
Reviewer: whizkid from APAC
sorry i meant to say the test enviroment is not the same.. it's a single instance.
amazing..
May 9, 2005 - 12pm Central time zone
Reviewer: whizkid from APAC
words fail me.. worked like a charm.. < 1 sec.. seeing such limited information, how did you make
such a brilliant suggestion? if you could share how you got idea to concatenate all the three
columns, it would really help in my approach towards tuning queries in future.
Followup May 9, 2005 - 1pm Central time zone:
1 TABLE ACCESS BY INDEX ROWID OBJ#(48196)
30881 INDEX RANGE SCAN OBJ#(48199) (object id 48199)
When I see that you got hits from the index 30,881 times
And went to the table 30,881 times...
To get a single row.....
I think "missing a column or two in the index perhaps, so we don't have to go index->table, index->
table and so on..."
tom anything thoughts on how I can improve this query
May 10, 2005 - 12pm Central time zone
Reviewer: Janis
SELECT wo.order_id, ic.l1, ic.l2, ic.l3, wo.to_block, wo.to_row, wo.to_stack, ic.out_vessel_code,
ic.status_code, ic.container_id, ic.hazardous, ic.out_of_service, ic.over_dimensions,
ic.reefer, wo.manual_location, cm.ssl_owner_code, cm.lht_code, ic.gross_weight,
wo.move_type, ic.out_port_of_discharge,ic.In_mode,ic.container,ic.out_mode
FROM work_orders wo, inv_containers ic, container_masters cm
WHERE wo.queue_code = 'EXREL' AND wo.assigned_date is null AND wo.HOLD ='N'
AND wo.ORDER_PROBLEM IS NULL AND 70000 > ic.gross_weight
AND MOVE_TYPE = 'Y' AND wo.INV_CONTAINER_ID = ic.CONTAINER_ID
AND (SELECT max(yb.yard_id) FROM yard_blockouts_temp yb, tml_blockout_reasons t, Yards y
WHERE t.safety_blockout = 'Y' AND Y.ACTIVE = 1
AND ic.l1 = yb.l1 AND (ic.l2 = yb.l2 OR yb.l2 IS null)
AND y.yard_id = yb.yard_id AND yb.reason_code = t.code) is null
AND ((ic.reefer = 1 and plug_unplug = 'U') OR (ic.reefer = 0))
AND (('EXREL' = 'EXREL'
AND (SELECT count(1) FROM container_holds where container = ic.CONTAINER) = 0)
OR 'EXREL' <> 'EXREL')
AND ic.CONTAINER = cm.CONTAINER
ORDER BY wo.priority,ic.l1,ic.l2,ic.l3 asc, ic.l4 DESC
Followup May 10, 2005 - 1pm Central time zone:
using cbo I would be tempted to replace
AND (SELECT count(1) FROM container_holds where container =
ic.CONTAINER) = 0)
OR 'EXREL' <> 'EXREL')
with
and ic.container not in ( select container
from container_holds
where container is not null )
and it looks like the other subquery is a "not in" candidate as well perhaps.
thanks!!!
May 10, 2005 - 1pm Central time zone
Reviewer: A reader
Rewrite SQL
May 17, 2005 - 8am Central time zone
Reviewer: mo from Malaysia
Hi Tom,
Is there a way to improve the SQL or any rewrite oppurtunity ? Thanks in advance.
GL_JE_HEADERS.NAME has a lots of distinct value.
GL_JE_HEADERS consists of 559130 rows
GL_JE_BATCHES consists of 220578 rows
INDEXES
GL_JE_HEADERS_U1 HEADER_ID 1
GL_JE_HEADERS_U2 NAME 1
GL_JE_HEADERS_U2 JE_BATCH_ID 2
GL_JE_BATCHES_U1 JE_BATCH_ID 1
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=6694 Card=140274 Byt
es=9117810)
1 0 SORT (GROUP BY) (Cost=6694 Card=140274 Bytes=9117810)
2 1 NESTED LOOPS (Cost=3154 Card=483222 Bytes=31409430)
3 2 TABLE ACCESS (FULL) OF 'GL_JE_HEADERS' (Cost=3154 Card
=484116 Bytes=28562844)
4 2 INDEX (UNIQUE SCAN) OF 'GL_JE_BATCHES_U1' (UNIQUE)
===================SQL Statement================
select
jeh.name
from
gl.GL_JE_HEADERS jeh ,
gl.gl_je_batches bej
where
jeh.JE_HEADER_ID > 5000 and
jeh.je_batch_id = bej.je_batch_id and
jeh.JE_SOURCE NOT IN('Assets','Intercompany') and
jeh.NAME NOT IN ('Addition','Adjustment')
group by
jeh.name
=================================================
Rgds
Mo
Followup May 17, 2005 - 9am Central time zone:
wow, I cannot believe that is using an index. did you set the optimizer_* parameters way to the
left here?
you don't have hash joins disabled do you?
hint it to be ALL_ROWS and let it hash join.
Join not needed?
May 17, 2005 - 1pm Central time zone
Reviewer: Todor Botev from Germany
Just a short question to the above query to clarify the data logic:
You do not select anything from "gl_je_batches". Could it be that you do not need the join with
"gl_je_batches" at all?
Can the column "jeh.je_batch_id" have values other than the ones in the table "gl_je_batches". Is
there any kind of foreign key between the two tables?
Thanks for your valuable input
May 18, 2005 - 4am Central time zone
Reviewer: Mo from Malaysia
Hi Tom,
Thanks for your input. We have upgraded the database from 8.1.7.4 to 9.2.0.5 recently but never
modify the optimizer_*. The optimizer engine seems like favor NL instead of HASH join. Just wonder
why is the HASH join is better option over NL for my case. Is it coz of the driving table
(GL_JE_BATCHES 220578 rows) which is consider big ?
Current Setting.
optimizer_index_caching integer 0
optimizer_index_cost_adj integer 100
hash_area_size integer 131072
hash_join_enabled boolean TRUE
I have gathered the plan & stat for the diff scenarios.
CURRENT PLAN (PLAN 0)
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=6694 Card=140274 Byt
es=9117810)
1 0 SORT (GROUP BY) (Cost=6694 Card=140274 Bytes=9117810)
2 1 NESTED LOOPS (Cost=3154 Card=483222 Bytes=31409430)
3 2 TABLE ACCESS (FULL) OF 'GL_JE_HEADERS' (Cost=3154 Card
=484116 Bytes=28562844)
4 2 INDEX (UNIQUE SCAN) OF 'GL_JE_BATCHES_U1' (UNIQUE)
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 7019 8.85 17.36 20778 329691 0 105261
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 7021 8.85 17.37 20778 329691 0 105261
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 177 (ONSEMI)
Rows Row Source Operation
------- ---------------------------------------------------
105261 SORT GROUP BY
308888 NESTED LOOPS
308888 TABLE ACCESS FULL OBJ#(33979)
308888 INDEX UNIQUE SCAN OBJ#(33939) (object id 33939)
Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: CHOOSE
105261 SORT (GROUP BY)
308888 NESTED LOOPS
308888 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'GL_JE_HEADERS'
308888 INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'GL_JE_BATCHES_U1'
(UNIQUE)
PLAN 1
USE HASH HINT (USE_HASH)
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=7890 Card=140274 Byt
es=9117810)
1 0 SORT (GROUP BY) (Cost=7890 Card=140274 Bytes=9117810)
2 1 HASH JOIN (Cost=4350 Card=483222 Bytes=31409430)
3 2 INDEX (FULL SCAN) OF 'GL_JE_BATCHES_U1' (UNIQUE) (Cost
=613 Card=220050 Bytes=1320300)
4 2 TABLE ACCESS (FULL) OF 'GL_JE_HEADERS' (Cost=3154 Card
=484116 Bytes=28562844)
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 7019 8.46 19.38 20790 21415 0 105261
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 7021 8.46 19.38 20790 21415 0 105261
Rows Row Source Operation
------- ---------------------------------------------------
105261 SORT GROUP BY
308888 HASH JOIN
220614 INDEX FULL SCAN OBJ#(33939) (object id 33939)
308888 TABLE ACCESS FULL OBJ#(33979)
Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: CHOOSE
105261 SORT (GROUP BY)
308888 HASH JOIN
220614 INDEX GOAL: ANALYZED (FULL SCAN) OF 'GL_JE_BATCHES_U1'
(UNIQUE)
308888 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'GL_JE_HEADERS'
PLAN 2
alter session set optimizer_index_cost_adj =30;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=5600 Card=140274 Byt
es=9117810)
1 0 SORT (GROUP BY) (Cost=5600 Card=140274 Bytes=9117810)
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'GL_JE_HEADERS' (Cost=2
Card=2 Bytes=118)
3 2 NESTED LOOPS (Cost=2060 Card=483222 Bytes=31409430)
4 3 INDEX (FULL SCAN) OF 'GL_JE_BATCHES_U1' (UNIQUE) (Co
st=613 Card=220050 Bytes=1320300)
5 3 INDEX (RANGE SCAN) OF 'GL_JE_HEADERS_N1' (NON-UNIQUE
) (Cost=2 Card=6)
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 7019 22.28 66.46 23176 595352 0 105261
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 7021 22.29 66.47 23176 595352 0 105261
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 177 (ONSEMI)
Rows Row Source Operation
------- ---------------------------------------------------
105261 SORT GROUP BY
308888 TABLE ACCESS BY INDEX ROWID OBJ#(33979)
779786 NESTED LOOPS
220614 INDEX FULL SCAN OBJ#(33939) (object id 33939)
559171 INDEX RANGE SCAN OBJ#(33988) (object id 33988)
Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: CHOOSE
105261 SORT (GROUP BY)
308888 NESTED LOOPS
779786 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'GL_JE_HEADERS'
220614 INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'GL_JE_BATCHES_U1'
(UNIQUE)
Not much gain from CPU resource when changing from NL (8.85) to HASH join (8.46) but there is a
major improvement in query (21415 for HASH and 329691 for NL).
When I look at the Explain Plan, the total cost for NL is smaller than HASH. Why is that so?
SELECT STATEMENT Optimizer=CHOOSE (Cost=7890) for HASH
SELECT STATEMENT Optimizer=CHOOSE (Cost=6694) for NL
I tried to force the PLAN to use the index by changing the value of optimizer_index_cost_adj=30. No
luck. The performance becomes worst.
Rgds
Mo
Any thoughts on this query. It taking a few min. to run
May 18, 2005 - 5pm Central time zone
Reviewer: lou
SELECT ssl_user_code,
i.container,
out_date,
cl.code LENGTH_CODE,
out_trucker_code,
decode ((SELECT ih.in_date
FROM his_containers ih
WHERE ih.container = i.container
AND ih.container_id > i.container_id
AND ih.container_id = (SELECT MIN(ihh.container_id)
FROM his_containers ihh
WHERE ihh.container_id > i.container_id
AND ihh.container = i.container)), NULL,
(SELECT ic.in_date
FROM inv_containers ic
WHERE ic.container = i.container)) IN_DATE,
decode ((SELECT ih.in_trucker_code
FROM his_containers ih
WHERE ih.container = i.container
AND ih.container_id > i.container_id
AND ih.container_id = (SELECT MIN(ihh.container_id)
FROM his_containers ihh
WHERE ihh.container_id > i.container_id
AND ihh.container = i.container)), NULL,
(SELECT ic.in_trucker_code
FROM inv_containers ic
WHERE ic.container = i.container)) IN_TRUCKER_CODE,
decode ((SELECT ih.chassis
FROM his_containers ih
WHERE ih.container = i.container
AND ih.container_id > i.container_id
AND ih.container_id = (SELECT MIN(ihh.container_id)
FROM his_containers ihh
WHERE ihh.container_id > i.container_id
AND ihh.container = i.container)), NULL,
(SELECT ic.chassis
FROM inv_containers ic
WHERE ic.container = i.container)) IN_CHASSIS
FROM HIS_containers i,
container_masters cm,
tml_container_lhts clht,
tml_container_lengths cl
WHERE i.chassis IS NULL AND
i.out_mode = 'T' AND
i.out_date BETWEEN to_date ('1-JAN-2005','DD-MON-YYYY') AND to_date
('01-FEB-2005','DD-MON-YYYY') AND
cm.container = i.container AND
cm.lht_code = clht.code AND
clht.length_code = cl.code AND
decode ((SELECT ih.container_id
FROM his_containers ih
WHERE ih.container = i.container
AND ih.container_id > i.container_id
AND ih.container_id = (SELECT MIN(ihh.container_id)
FROM his_containers ihh
WHERE ihh.container_id > i.container_id
AND ihh.container = i.container)), NULL,
(SELECT ic.container_id
FROM inv_containers ic
WHERE ic.container = i.container)) IS NOT NULL
/
Carry's method
May 20, 2005 - 7am Central time zone
Reviewer: Gaurav Chandra Pandey from INDIA
Hi Tom,
I have your performance book as well as Carry Milsap's oracle performance book..I want to know
which method to follow..cause according to cary one should use YAPP method, instead of using
traditional checking hit ratio method.
Followup May 20, 2005 - 8am Central time zone:
not sure what you are asking.
but, if I tell you you have a 99.532% cache hit, is that
a) good
b) bad
c) neither good nor bad
you need to look at lots of things, not just a ratio, the ratio can be used by only in the context
of lots of other bits of data.
SQL tuning
May 20, 2005 - 3pm Central time zone
Reviewer: A reader
Hi Tom,
I was going through this particular thread on SQL tuning. It gives more insight into SQL tuning
than most of the "popular" SQL Tuning books do. Keep up the good work
thanks
Anto

May 23, 2005 - 11am Central time zone
Reviewer: Alex
Hi,
I am in the process of tuning some sql statements that were identified as needing some work. I'm
stuck on one at the moment that runs much better on one database than the other. I'm hoping that
if you look at the differences between the access paths you might be able to point me at some
things to look for to account for these differences.
First the 9 second one:
18 0 TABLE ACCESS (BY INDEX ROWID) OF 'COMPLETED_REQ' (Co
st=1 Card=2 Bytes=94)
19 18 NESTED LOOPS (Cost=7512 Card=6030 Bytes=795960)
20 19 TABLE ACCESS (FULL) OF 'SCHEDULED_REQ' (Cost=534
9 Card=3604 Bytes=306340)
21 19 INDEX (RANGE SCAN) OF 'COMPLETED_REQ#PK' (UNIQUE
) (Cost=1 Card=1)
Now the 25 second version:
18 0 MERGE JOIN (Cost=4006 Card=2 Bytes=244)
19 18 TABLE ACCESS (BY INDEX ROWID) OF 'SCHEDULED_REQ' (
Cost=248 Card=3266 Bytes=277610)
20 19 INDEX (FULL SCAN) OF 'SCHEDULED_REQ#PK' (UNIQUE)
(Cost=26 Card=627108)
21 18 SORT (JOIN) (Cost=3758 Card=408010 Bytes=15096370)
22 21 TABLE ACCESS (FULL) OF 'COMPLETED_REQ' (Cost=100
1 Card=408010 Bytes=15096370)
As you can tell I omitted the first 17 steps to save space, they were identical in both plans so I
only posted where the differences lye. I will try to give you more information if you need it, but
I am more or less looking for your opinion on what you see. Thanks.
Followup May 23, 2005 - 2pm Central time zone:
optimizer parameters are the same?
stats are similar?
data is logically the same?
about the suggestion above..
May 23, 2005 - 12pm Central time zone
Reviewer: whizkid from APAC
http://asktom.oracle.com/pls/ask/f?p=4950:61:11613347383759103782::::P61_ID:8764516459743#4048900169
8671
Tom, thanks to your input above, the query is able to run very fast.. but after implementing the
same in production, our enqueue waits have gone up dramatically.. maybe because of the newly
created index, the insert is slow?? i know this is not sufficient information for you to give any
comments, what do you think I should look at? should I drop that index and look for some
alternative method?
Followup May 23, 2005 - 3pm Central time zone:
doesn't this just mean "we removed a bottleneck, so something running really slow before isn't and
now we get lots more enqueue waits (locked rows, we are waiting on others to unlock the row)
because we ARRIVE AT THE ROW faster"
Maybe because of the newly created index - the query is so fast that your enqueue waits went up
because everyone is going *faster*
Did your overall processing rate go UP or DOWN?
(enqueue waits are row locks mostly -- they would not result from adding a non-unique index)

May 23, 2005 - 3pm Central time zone
Reviewer: Alex
Tom,
Yes parameters are the same. The stats are current. The faster running DB has slightly less rows,
completed_req table has 15541 fewer, scheduled_req 2583.
I tried to use a hint to get the optimizer to use the index range scan but it won't take. Is there
a specific index hint for range scan like index_ffs for fast full scan?

May 23, 2005 - 3pm Central time zone
Reviewer: Alex
Forget it, sorry to bother you. It's using the same plan but running in 7 seconds now. I must
have missed a table when I computed stats or something.
yes..
May 24, 2005 - 1am Central time zone
Reviewer: whizkid from APAC
we did remove a bottleneck, but if we see the overall performance, it has gone down. the process is
like the user will add the customer, enter some details, get the account balance, issue the
policy... the 3rd part of getting the account balance is really fast now.. but during the final
part of issuing the policy, sometimes there are lots of enqueue waits.. when i find out the holder
of the lock from v$lock, the holder is executing a query on the table on which we created the
index.. yesterday we had around 90 sessions (2 nodes included) waiting for enqueue. i had to kill
all the holders to get back to normal working.. the users have also started complaining that at the
final submit button the system just hangs (maybe because of the enqueue wait)..
still unable to get the logic as to why do we wait for locked rows if we create a new index...
Followup May 24, 2005 - 7am Central time zone:
the holder is holding a lock -- the query they are currently executing is "not meaningful", it is
just what they are doing now. The lock they took might have been taken 10 second ago.
Print out a lock graph (say using utllockt in $ORACLE_HOME/rdbms/admin) to see the blockers and
blockees
See what the BLOCKEES are waiting on -- what table, that'll give you an idea of where the locking
issue is.
(you don't have unindexed foreign keys do you??)
I fully believe the index is a red herring here, it is not the issue.
You are correct!
May 24, 2005 - 8am Central time zone
Reviewer: whizkid from APAC
The index was a red herring.. (I also learned a new term today!).. I found out the SQL of the
blocking session and tried to analyze that.. it was taking a long time and had a missing index.. i
created an index.. that query is working fast and now there are no more enqueue waits... this
problem is solved .. at least i think so..
But Tom, really before creating the index that you suggested there were really no enqueue waits..
still dont understand how creating that shot up the wait events.. maybe as you said since we are
getting the row faster, it is going ahead and waiting for something.. but the above index which i
created (from the blocking session) was not there and no enqueue waits were there earlier.. is it
because earlier the 1st query was slow, 2nd query was also slow so no difference (no enqueue).. now
1st query was fast 2nd query was slow so people had to wait.. is this it?
thanks a ton once again...
Followup May 24, 2005 - 10am Central time zone:
you were doing something like:
run query1
modify row
run query2
do something
commit
query1 was a bottleneck, a big one. everyone was taking a long time to get through that. few
would simultaneously try to get the "modify row" at the same time. one would and run query2. now
query2 was slow but not nearly as bad as query1 where people spent MOST of their time. So, the
number of people waiting on the enqueue was few.
you speed up query1 -- now everyone is stuck on enqueue waits.
(this is a hypothesis... a guess)
parse time
May 25, 2005 - 12pm Central time zone
Reviewer: Branka from VA, USA
Tom,
What do you think about parse time now? I had discussion with another DBA, who insisted that parse
time is not important any more, since CPU become so fast.
Thanks
Followup May 25, 2005 - 3pm Central time zone:
hehehehe BAMM (ouch, that hurt. I fell out of my chair.)
Umm, that would be "not correct" in a big way.
tell them to find a multi-cpu machine.
then write three stored procedures:
a) one that does an
execute immediate 'insert into t t' || job_number || ' values ( ' || i || ')';
(eg: insert into t t5 values ( 1 );
insert into t t5 values ( 2 );
....)
b) one that does
execute immediate 'insert into t t' mod(i,2) ||
' values ( :x )' using i;
(eg: insert into t t0 values ( :x );
insert into t t1 values ( :x );
insert into t t0 values ( :x );
c) one that does
insert into t values ( i );
in a for loop with i running from 1 to 25,000
Now, run one of them -- measure cpu and elapsed time.
Now run two of them -- measure cpu and elapsed time.
Repeat up to say 10.
Graph it.
I did. Ask yourself why it takes as much cpu for 10 users to insert 25,000 rows
using method (C) as it did for TWO users using method (A)??
Or why 10 users using method (C) used the cpu of 4.5 users using method (B)
Or why 10 users could create 250,000 (method C) rows IN THE SAME TIME one user (method A) could
create 25,000 rows?
Or why 10 users could create 250,000 rows (method C) in the same time 4 users (method B) could
create 100,000 rows?
It is all about latching -- latches are a type of lock, locks are serialization devices,
serialization devices inhibit scalability.
You want to kill a system, easy. Just parse as much as you can, that'll toast it right up. All
you'll ever be able to prove is you always need ONE MORE CPU on such a system (and never ever get
any additional performance -- you are all trying to latch the same resource, the shared pool -
remember it is the SHARED pool, not BOB'S pool, not MARY'S pool -- the shared pool)
see also
http://www.jlcomp.demon.co.uk/proof.html
Selects on views with unions
May 27, 2005 - 3pm Central time zone
Reviewer: Philihp Busby from Cary, NC USA
Hi Tom,
An application we use provides a view as an abstraction layer for legacy applications. The table
structures are as follows.
CREATE TABLE person (
id NUMBER(5) PRIMARY KEY,
name VARCHAR2(255)
);
CREATE UNIQUE INDEX person_pdash_idx ON person ('P-'||id);
INSERT INTO person (id,name) VALUES (20001,'Craft');
INSERT INTO person (id,name) VALUES (20001,'Dover');
CREATE TABLE company (
id NUMBER(5) PRIMARY KEY,
name VARCHAR2(255)
);
CREATE UNIQUE INDEX person_pdash_idx ON person ('C-'||id);
INSERT INTO person (id,name) VALUES (10001,'Alice');
INSERT INTO person (id,name) VALUES (10001,'Bryan');
CREATE VIEW customer_v AS
SELECT
'PERS' AS type
'P-'||p.id as id
FROM person p
UNION ALL
SELECT
'COMP' AS type
'C-'||c.id AS id
FROM company c
The indexes speed up queries like
SELECT * FROM company WHERE 'C-'||id = 'C-20001'
SELECT * FROM person WHERE 'P-'||id = 'P-10001'
but no advantage is given to the following (which happens in the *other* part of the union, and
could also NEVER happen for obvious reasons)
SELECT * FROM company WHERE 'C-'||id = 'P-20001'
SELECT * FROM person WHERE 'P-'||id = 'C-10001'
When our legacy systems query the customer_v view as
SELECT *
FROM customer_v
WHERE type = 'PERS'
AND id = 'P-'||10002
which is like querying
SELECT *
FROM (
SELECT
'PERS' AS type
'P-'||p.id as id
FROM person p
UNION ALL
SELECT
'COMP' AS type
'C-'||c.id AS id
FROM company c
)
WHERE type = 'PERS'
AND id = 'P-'||10002
Since these tables themselves are accessed much from newer apps, instead of legacy apps, it would
be preferable not to introduce a second 'P-' index on the Company table, and/or a second 'C-' index
on Person.
Is there a way to hint to the optimizer somehow that a row *WILL NOT* be found in a unioned
"branch" query of a view? In a query where type="PERS", I would like it to not attempt searching
the Company table.
Followup May 27, 2005 - 4pm Central time zone:
but it should be doing that already? 9ir2 and 10gr1 did anyhow. After fixing up your example to
be what you said it was:
CREATE TABLE person (
id NUMBER(5) PRIMARY KEY,
name VARCHAR2(255)
);
CREATE UNIQUE INDEX person_pdash_idx ON person ('P-'||id);
INSERT INTO person (id,name) VALUES (20001,'Craft');
INSERT INTO person (id,name) VALUES (20002,'Dover');
exec dbms_stats.set_table_stats( user, 'PERSON', numrows=>1000000, numblks=>100000 );
CREATE TABLE company (
id NUMBER(5) PRIMARY KEY,
name VARCHAR2(255)
);
CREATE UNIQUE INDEX comp_cdash_idx ON company ('C-'||id);
INSERT INTO company (id,name) VALUES (10001,'Alice');
INSERT INTO company (id,name) VALUES (10002,'Bryan');
exec dbms_stats.set_table_stats( user, 'COMPANY', numrows=>1000000, numblks=>100000 );
CREATE or replace VIEW customer_v
AS
select 'PERS' as type, 'P-' || p.id as id, name from person p
union all
select 'COMP' as type, 'C-' || c.id as id, name from company c;
(index names, tablenames, create views etc -- were wrong).....
SELECT * FROM customer_v WHERE type = 'PERS' AND id = 'P-'||20002
call count cpu elapsed disk 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 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.00 0.00 0 2 0 1
Rows Row Source Operation
------- ---------------------------------------------------
1 VIEW (cr=2 r=0 w=0 time=67 us)
1 UNION-ALL (cr=2 r=0 w=0 time=60 us)
1 TABLE ACCESS BY INDEX ROWID PERSON (cr=2 r=0 w=0 time=27 us)
1 INDEX UNIQUE SCAN PERSON_PDASH_IDX (cr=1 r=0 w=0 time=14 us)(object id 42089)
0 FILTER (cr=0 r=0 w=0 time=1 us)
0 TABLE ACCESS BY INDEX ROWID COMPANY (cr=0 r=0 w=0 time=0 us)
0 INDEX UNIQUE SCAN COMP_CDASH_IDX (cr=0 r=0 w=0 time=0 us)(object id 42092)
********************************************************************************
SELECT * FROM customer_v WHERE type = 'COMP' AND id = 'C-'||10002
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.00 0.00 0 2 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.01 0.00 0 2 0 1
Rows Row Source Operation
------- ---------------------------------------------------
1 VIEW (cr=2 r=0 w=0 time=55 us)
1 UNION-ALL (cr=2 r=0 w=0 time=49 us)
0 FILTER (cr=0 r=0 w=0 time=1 us)
0 TABLE ACCESS BY INDEX ROWID PERSON (cr=0 r=0 w=0 time=0 us)
0 INDEX UNIQUE SCAN PERSON_PDASH_IDX (cr=0 r=0 w=0 time=0 us)(object id 42089)
1 TABLE ACCESS BY INDEX ROWID COMPANY (cr=2 r=0 w=0 time=27 us)
1 INDEX UNIQUE SCAN COMP_CDASH_IDX (cr=1 r=0 w=0 time=14 us)(object id 42092)
That shows it is only executing one or the other part of the plan at runtime -- the filter step is
cutting out the branch that need not be executed. zero IO.
Different ways of writting same sql
May 30, 2005 - 6am Central time zone
Reviewer: Reader from San Francisco, CA
Suppose in our emp-dept table ,
We have a deptno in Dept table where there is no employees in Emp table.
I would like to select that deptno.
SQL> select deptno from dept where deptno not in(select distinct deptno from emp);
I want to know if there are any other alternatives to write this statment in term of cost other
than the followin:
a) SCOTT@9R2> select deptno
2 from scott.dept
3 where not (deptno in (select distinct deptno from scott.emp));
b) SCOTT@9R2> select dept.deptno
2 from scott.dept, scott.emp
3 where dept.deptno = emp.deptno (+)
4 and emp.deptno is null;
c) select deptno from scott.dept where deptno not in(select distinct deptno from scott.emp);
Explain Plans for all the above SQL's
========================================
SCOTT@9R2> SPOOL SQLS.LST
SCOTT@9R2> select deptno
2 from scott.dept
3 where not (deptno in (select distinct deptno from scott.emp));
DEPTNO
----------
40
Execution Plan
---------------------------------------------------------- 0 SELECT STATEMENT
Optimizer=CHOOSE (Cost=446694 Card=1 Bytes=
26) 1 0 MERGE JOIN (ANTI) (Cost=446694 Card=1 Bytes=26)
2 1 INDEX (FULL SCAN) OF 'DEPT_PRIMARY_KEY' (UNIQUE) (Cost=2
6 Card=10000000 Bytes=130000000) 3 1
SORT (UNIQUE) (Cost=446668 Card=10000000 Bytes=130000000
)
4 3 TABLE ACCESS (FULL) OF 'EMP' (Cost=1552 Card=10000000
Bytes=130000000)
Statistics
---------------------------------------------------------- 0 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
305 bytes sent via SQL*Net to client
426 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
SCOTT@9R2> select dept.deptno
2 from scott.dept, scott.emp
3 where dept.deptno = emp.deptno (+)
4 and emp.deptno is null;
DEPTNO
----------
40
Execution Plan
---------------------------------------------------------- 0 SELECT STATEMENT
Optimizer=CHOOSE (Cost=446694 Card=10000000
Bytes=260000000)
1 0 FILTER
2 1 MERGE JOIN (OUTER) 3 2
INDEX (FULL SCAN) OF 'DEPT_PRIMARY_KEY' (UNIQUE) (Cost
=26 Card=10000000 Bytes=130000000) 4 2
SORT (JOIN) (Cost=446668 Card=10000000 Bytes=130000000
)
5 4 TABLE ACCESS (FULL) OF 'EMP' (Cost=1552 Card=1000000
0 Bytes=130000000)
Statistics
---------------------------------------------------------- 0 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
305 bytes sent via SQL*Net to client
426 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
SCOTT@9R2> select deptno from scott.dept where deptno not in(select distinct deptno from
scott.emp);
DEPTNO
----------
40
Execution Plan
---------------------------------------------------------- 0 SELECT STATEMENT
Optimizer=CHOOSE (Cost=446694 Card=1 Bytes=
26)
1 0 MERGE JOIN (ANTI) (Cost=446694 Card=1 Bytes=26)
2 1 INDEX (FULL SCAN) OF 'DEPT_PRIMARY_KEY' (UNIQUE) (Cost=2
6 Card=10000000 Bytes=130000000) 3 1
SORT (UNIQUE) (Cost=446668 Card=10000000 Bytes=130000000
)
4 3 TABLE ACCESS (FULL) OF 'EMP' (Cost=1552 Card=10000000
Bytes=130000000)
Statistics
---------------------------------------------------------- 0 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
305 bytes sent via SQL*Net to client
426 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
SCOTT@9R2> SPOOL OFF;
Actually Im tryin to practice using explain plan and know the difference.
You time is precious and any response to this will be highly appreciated;
Followup May 30, 2005 - 9am Central time zone:
the best way to write this is:
select deptno from dept where deptno not in (select deptno from emp);
period, plain and simple. IF deptno is NULLABLE in emp, you might
select deptno from dept where deptno not in (select deptno from emp where deptno is not null);
(else, if there is one NULL deptno in EMP, the result set is always "empty")
Looks like you disabled hash joins in your database, I would expect brutally efficient hash
anti-joins, not the not as efficient for big things merge anti-join.
you have "where not exists" as well as not in and the "anti join" you coded.
You always have a "just outer join and use HAVING COUNT(emp.empno) = 0" as well
How to know if Hash Join is disabled?
May 31, 2005 - 4am Central time zone
Reviewer: Reader from San Francisco, CA
<QUOTE>
Looks like you disabled hash joins in your database, I would expect brutally efficient hash
anti-joins, not the not as efficient for big things merge anti-join.
</QUOTE>
I would like to know how u knew that Hash joins are disabled in my DB and Im really not clear about
" I would expect brutally efficient hash anti-joins, not the not as efficient for big things merge
anti-join" Could u please help me in understanding this? And yeah how can I enable hash join if its
disabled?
Here im pasting explain plan for one more query which is using hash joins? Im really not clear
about my hash joins disabled :(
NK_DBA@orakic> select rpad(' ',2*level,' ') || name name
2 from (select dname name, deptno id, to_number(null) parent
3 from dept
4 union all
5 select ename ename, to_number(null) id, deptno parent
6 from emp )
7 start with parent is null
8 connect by prior id = parent
9 /
NAME
-----------------------------------------------------------------------
ACCOUNTING
CLARK
KING
MILLER
RESEARCH
SMITH
JONES
SCOTT
ADAMS
FORD
SALES
ALLEN
WARD
MARTIN
BLAKE
TURNER
JAMES
OPERATIONS
18 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3104 Card=20000000 B
ytes=700000000)
1 0 CONNECT BY (WITH FILTERING)
2 1 FILTER
3 2 COUNT
4 3 VIEW (Cost=3104 Card=20000000 Bytes=700000000)
5 4 UNION-ALL
6 5 TABLE ACCESS (FULL) OF 'DEPT' (Cost=1552 Card=10
000000 Bytes=220000000)
7 5 TABLE ACCESS (FULL) OF 'EMP' (Cost=1552 Card=100
00000 Bytes=200000000)
8 1 HASH JOIN
9 8 CONNECT BY PUMP
10 8 COUNT
11 10 VIEW (Cost=3104 Card=20000000 Bytes=700000000)
12 11 UNION-ALL
13 12 TABLE ACCESS (FULL) OF 'DEPT' (Cost=1552 Card=10
000000 Bytes=220000000)
14 12 TABLE ACCESS (FULL) OF 'EMP' (Cost=1552 Card=100
00000 Bytes=200000000)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
12 consistent gets
0 physical reads
0 redo size
687 bytes sent via SQL*Net to client
437 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
18 rows processed
I hope to hear you and better understand about your point of view.
Followup May 31, 2005 - 8am Central time zone:
"U" didn't know. "U" doesn't even look at this site, but "U" does get lots of requests for
appearances.
I guessed -- because of the sort merges, I would expect hash operations. show parameter hash would
tell you one way or the other.
are you using manual or automatic PGA memory management and what is your pga aggregate target and
hash area sizes set to
dedicated server or shared server connection?
Hash Enabled or disabled?
June 1, 2005 - 2am Central time zone
Reviewer: Reader from San Francisco, CA
I'm extreamly sorry for using "U", I really didn't noticed it... But I will take care of it next
time.
My Server's Tnsname.ora says:
# TNSNAMES.ORA Network Configuration File: /ora/oracle/network/admin/tnsnames.ora
# Generated by Oracle configuration tools.
ORAKIC =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = kic-compaq)(PORT = 1521))
)
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = orakic)
)
)
AS1200 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = as1200)(PORT = 1521))
)
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = orakic)
)
)
ES40 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = legatosrv)(PORT = 1521))
)
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = orakic)
)
)
INST1_HTTP =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = kic-compaq)(PORT = 1521))
)
(CONNECT_DATA =
(SERVER = SHARED)
(SERVICE_NAME = MODOSE)
(PRESENTATION = http://HRService
)
)
EXTPROC_CONNECTION_DATA =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
)
(CONNECT_DATA =
(SID = PLSExtProc)
(PRESENTATION = RO)
)
)
So I guess we are using deticated server connection, I checked it from v$session and found all the
connections are using Dedicated server.
In 9iR2 the default is dedicated where in 10gR1 its shared right?
Following are the parameters about PGA, HASH and others
SYS@PROD>SHOW PARAMETER WORKAREA_SIZE_POLICY
NAME TYPE VALUE
------------------------------------ ----------- -------------------
workarea_size_policy string AUTO
SYS@PROD>SHOW PARAMETER PGA
NAME TYPE VALUE
------------------------------------ ----------- -------------------
pga_aggregate_target big integer 16777216
SYS@PROD>SHOW PARAMETER HASH
NAME TYPE VALUE
------------------------------------ ----------- -------------------
hash_area_size integer 2048000
hash_join_enabled boolean FALSE
SYS@PROD>SHOW PARAMETER SHARED
NAME TYPE VALUE
------------------------------------ ----------- --------------------
hi_shared_memory_address integer 0
max_shared_servers integer 20
shared_memory_address integer 0
shared_pool_reserved_size big integer 42781900
shared_pool_size big integer 855638016
shared_server_sessions integer 563
shared_servers integer 1
Can I know what is the other way to check about PGA memory management other than show parameter
WORKAREA_SIZE_POLICY?
Response to my query from your precious time will be highly appreciated. I will always treasure the
information I'm getting from your forms.
Thanks a million for your time.
Followup June 1, 2005 - 8am Central time zone:
you have a 16meg pga aggregate target, which is tiny.
that is 16meg to be used by all concurrent sessions -- total.
is that "right" for you?
sql..
June 1, 2005 - 11am Central time zone
Reviewer: whizkid from APAC
any clue how can i tune the below query? dont know why it is doing a full table scan..
DELETE FROM ACC_LOGICAL_LOCKS
WHERE
LOCKED_BY_OBJECT_TYPE = :b1 AND LOCKED_BY_OBJECT_ID = :b2
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.24 2.28 2893 3211 0 0
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 2 0.24 2.28 2893 3211 0 0
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 5386
Rows Row Source Operation
------- ---------------------------------------------------
0 DELETE
0 TABLE ACCESS FULL ACC_LOGICAL_LOCKS
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
global cache cr request 1224 0.00 0.07
db file scattered read 527 0.03 1.91
db file sequential read 17 0.01 0.02
buffer busy waits 4 0.00 0.01
buffer busy global CR 3 0.00 0.02
SQL*Net message to client 1 0.00 0.00
SQL*Net message from client 1 25.36 25.36
row cache lock 1 0.00 0.00
the time taken is very minute but this query gets executed over 500 times in 30 minutes.. from
statspack of 30 minute window..
Physical Reads Executions Reads per Exec %Total Time (s) Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
1,101,029 508 2,167.4 26.8 138.24 315.62 4242414595
Module: JDBC Thin Client
DELETE FROM ACC_LOGICAL_LOCKS WHERE LOCKED_BY_OBJECT_TYPE = :b1
AND LOCKED_BY_OBJECT_ID = :b2
Buffer Gets Executions Gets per Exec %Total Time (s) Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
1,631,797 508 3,212.2 0.9 138.24 315.62 4242414595
Module: JDBC Thin Client
DELETE FROM ACC_LOGICAL_LOCKS WHERE LOCKED_BY_OBJECT_TYPE = :b1
AND LOCKED_BY_OBJECT_ID = :b2
there's a primary key (and hence a unique index) on
ACC_LOGICAL_LOCKS(LOCKED_OBJECT_TYPE, LOCKED_OBJECT_ID)
the object is analyzed and it has 400k records..
version is 9.2.0.5
Followup June 1, 2005 - 12pm Central time zone:
what are the datatypes of the columns, and how are the java developers binding to them?
ops$tkyte@ORA10G> create table t ( x varchar2(10), y varchar2(10), z int, primary key(x,y) );
Table created.
ops$tkyte@ORA10G> exec dbms_stats.set_table_stats(user,'T',numrows=>10000, numblks=>3211 );
PL/SQL procedure successfully completed.
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> declare
2 l_x1 number := 42;
3 l_y1 number := 42;
4 l_x2 varchar2(10) := 42;
5 l_y2 varchar2(10) := 42;
6 begin
7 for x in ( select * from t t1 where x= l_x1 and y = l_y1 )
8 loop
9 null;
10 end loop;
11
12 for x in ( select * from t t2 where x= l_x2 and y = l_y2 )
13 loop
14 null;
15 end loop;
16 end;
17 /
PL/SQL procedure successfully completed.
SELECT * FROM T T1 WHERE X= :B2 AND Y = :B1
Rows Row Source Operation
------- ---------------------------------------------------
0 TABLE ACCESS FULL T (cr=3 pr=0 pw=0 time=49 us)
********************************************************************************
SELECT * FROM T T2 WHERE X= :B2 AND Y = :B1
Rows Row Source Operation
------- ---------------------------------------------------
0 TABLE ACCESS BY INDEX ROWID T (cr=1 pr=0 pw=0 time=28 us)
0 INDEX UNIQUE SCAN SYS_C0017440 (cr=1 pr=0 pw=0 time=19 us)(object id 89706)
That'd be my first guess, they are doing implicit conversions, nullifying the ability to use the
index.

June 1, 2005 - 4pm Central time zone
Reviewer: A reader
Hi Whizkid,
What if you artificially increase the number of blocks for that table using
dbms_stats.set_table_Stats ?
Followup June 1, 2005 - 5pm Central time zone:
I still guess bad binds.
the rbo -- definitely would use an index.
the cbo -- almost certainly would use an index on a primary key, even on an empty table.
create table t ( x int, y int, z int, primary key(x,y) );
exec dbms_stats.gather_table_stats(user,'T');
variable x number
variable y number
exec :x := 42; :y := 55;
@trace
select * from t where x = :x and y = :y;
select *
from
t where x = :x and y = :y
Rows Row Source Operation
------- ---------------------------------------------------
0 TABLE ACCESS BY INDEX ROWID OBJ#(33728)
0 INDEX UNIQUE SCAN OBJ#(33729) (object id 33729)
datatypes are correct
June 2, 2005 - 12am Central time zone
Reviewer: whizkid from APAC
hi tom,
its getting called from pl/sql package.. it's not using any implicit conversion..
lotus >desc ACC_LOGICAL_LOCKS
Name Null? Type
----------------------------------------------------- --------
------------------------------------
LOCKED_OBJECT_TYPE NOT NULL VARCHAR2(10)
LOCKED_OBJECT_ID NOT NULL NUMBER
TIMESTAMP NOT NULL DATE
MOVEMENT_ID VARCHAR2(10)
USERNAME NOT NULL VARCHAR2(30)
SESSION_ID NOT NULL NUMBER
LOCKED_BY_OBJECT_TYPE VARCHAR2(10)
LOCKED_BY_OBJECT_ID NUMBER
i did this in sqlplus and gave the tkprof...
var b1 varchar2(20);
var b2 number;
exec :b1 := 'ACB';
exec :b2 := 15160174;
DELETE FROM ACC_LOGICAL_LOCKS
WHERE
LOCKED_BY_OBJECT_TYPE = :b1 AND LOCKED_BY_OBJECT_ID = :b2
create the correct index
June 2, 2005 - 3am Central time zone
Reviewer: Partha from Singapore
Hey whizkid,
--------------------------------------------------------------------
there's a primary key (and hence a unique index) on
ACC_LOGICAL_LOCKS(LOCKED_OBJECT_TYPE, LOCKED_OBJECT_ID)
------------------------------------------------------------------
The primary key is LOCKED_OBJECT_TYPE and LOCKED_OBJECT_ID, if you do not have an index on
"LOCKED_BY_OBJECT_TYPE" and "LOCKED_BY_OBJECT_ID", it is going to do a FULL table scan. If you
want to avoid full table scans on this (as it looks obvious based on the number of executions), you
need to create an index on (LOCKED_BY_OBJECT_TYPE, LOCKED_BY_OBJECT_ID) based on your requirement.
--------------------------------------------------------------------
DELETE FROM ACC_LOGICAL_LOCKS WHERE LOCKED_BY_OBJECT_TYPE = :b1 AND LOCKED_BY_OBJECT_ID = :b2
--------------------------------------------------------------------
SQL TO TUNE
June 2, 2005 - 9am Central time zone
Reviewer: Reddy from Canada
Hi Tom,
I had a table which has around 1,50,000 records and it has a very frequently executed query( say
every second) which ultimately causing high CPU usage.
Query:
-----
SELECT MIN (COLUMN1)
FROM TABLE1
WHERE COLUMN2 = (SELECT MIN (COLUMNN2)
FROM TABLE1
WHERE
COLUMN3 = 5 AND
COLUMN4 BETWEEN 200 AND 300)
AND COLUMN3 = 5 AND COLUMN4 BETWEEN 200 AND 300;
here column2 is a date.
Index on column4 is getting used in the plan.
PLAN_TABLE_OUTPUT
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 20 | 163 (0)|
| 1 | SORT AGGREGATE | | 1 | 20 | |
|* 2 | TABLE ACCESS BY INDEX ROWID | TABLE1 | 1 | 20 | 163 (0)|
|* 3 | INDEX RANGE SCAN | INDEX4 | 32722 | | 69 (0)|
| 4 | SORT AGGREGATE | | 1 | 15 | |
|* 5 | TABLE ACCESS BY INDEX ROWID| TABLE1 | 4675 | 70125 | 163 (0)|
|* 6 | INDEX RANGE SCAN | INDEX4 | 32722 | | 69 (0)|
-------------------------------------------------------------From statspack report:
Buffer Gets Executions Gets per Exec %Total Time (s) Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
1,189,308,938 4,968 239,393.9 69.8 ######## 25804.16 3961911181
How to reduce this huge number of buffergets for this query. Any better way of writing it?
Thanks,
Reddy.
Followup June 2, 2005 - 4pm Central time zone:
isn't that just
select *
from ( select column1
from t
where column3 = 5
and column4 between 200 and 300
order by column2, column1 )
where rownum = 1;
index on (colum3,column4,column2,column1)
To Reddy
June 2, 2005 - 10am Central time zone
Reviewer: Ricky
select * from (select column1
from table1
where column3 = 5
and column4 between 200 and 300
order by column2 asc , column1 asc
)where rownum <2
wouldn't the above work for you?
I believe Tom had written the above soln before for a similar qn...
Followup June 2, 2005 - 4pm Central time zone:
thanks, did see you did that already ;)
To Reddy
June 2, 2005 - 10am Central time zone
Reviewer: Jay
How about this:
select MIN(column1) KEEP (DENSE_RANK FIRST ORDER BY column2)
from table1
where column3 = 5
and column4 between 200 and 300
To Partha..
June 2, 2005 - 10am Central time zone
Reviewer: whizkid from APAC
Hey Partha..
thanks a lot...! need to get my eyes checked up for missing such an obvious thing :(
OK
June 4, 2005 - 11pm Central time zone
Reviewer: Kumar from Pune,India
Hi Tom,
Any better way to put this query??
select deptno,sal,count(*)
from emp
where sal in(select max(sal) from emp group by deptno)
group by deptno,sal
Followup June 5, 2005 - 9am Central time zone:
depends on what question you are trying to ask :)
You are asking for a count by deptno/sal
for the set of people
that make a salary that is the same as the max salary IN ANY department
it is proabably not what you meant. Did you mean -- show me by deptno, the salary that is the max
and the count of people making that salary.
Phrase as TEXT your question, your requirments. From there -- we can derive many possible queries
to answer.
http://tkyte.blogspot.com/2005/06/how-to-ask-questions.html
OK
June 5, 2005 - 10am Central time zone
Reviewer: Kumar from Pune,India
Hi Tom,
My requirement is
Get the deptno,max(sal) and count of people getting it.
Followup June 5, 2005 - 11am Central time zone:
count of people in that DEPTNO getting it
or
count of any person in any DEPTNO getting it.
you have the latter, i suspect the former is what you really want.
Index not used
June 6, 2005 - 5pm Central time zone
Reviewer: reader from USA
Following SQL is not using the index on the individual table
it took long time as the indiviudal table has 11 million records
SQL Statement from editor:
SELECT DISTINCT (h.household_id)
FROM household h,
individual i,
household_member hm
WHERE h.household_id = hm.household_id
AND i.individual_id = hm.individual_id
AND i.last_name LIKE 'L%'
AND i.first_name LIKE 'R%'
------------------------------------------------------------
Statement Id=7 Type=INDEX
Cost=1069 TimeStamp=06-06-05::16::28:38
(1) SELECT STATEMENT CHOOSE
Est. Rows: 11,541 Cost: 18,953
(9) SORT UNIQUE
Est. Rows: 11,541 Cost: 18,953
(8) HASH JOIN
Est. Rows: 11,541 Cost: 18,856
(3) PARTITION RANGE ALL
(2) TABLE ACCESS FULL COMPAS.INDIVIDUAL [Analyzed]
Blocks: 164,566 Est. Rows: 11,538 of 7,411,916 Cost: 10,031
(7) PARTITION RANGE ALL
(6) HASH JOIN
Est. Rows: 7,415,264 Cost: 7,958
(4) UNIQUE INDEX FAST FULL SCAN COMPAS.PK_HOUSEHOLD [Analyzed]
Est. Rows: 5,744,628 Cost: 1,069
(5) NON-UNIQUE INDEX FAST FULL SCAN COMPAS.HHLD_MEMB_LOCAL_02 [Analyzed]
Est. Rows: 7,415,264 Cost: 1,869
If I put the hint the cost of the query is very high but the 30% faster than the previous run.
SQL Statement from editor:
SELECT /*+ INDEX( i IDX_INDIVIDUAL_01) */ DISTINCT (h.household_id)
FROM household h,
individual i,
household_member hm
WHERE h.household_id = hm.household_id
AND i.individual_id = hm.individual_id
AND i.last_name LIKE 'L%'
AND i.first_name LIKE 'R%'
------------------------------------------------------------
Statement Id=7 Type=INDEX
Cost=1069 TimeStamp=06-06-05::17::09:06
(1) SELECT STATEMENT CHOOSE
Est. Rows: 11,541 Cost: 20,268
(9) SORT UNIQUE
Est. Rows: 11,541 Cost: 20,268
(8) HASH JOIN
Est. Rows: 11,541 Cost: 20,171
(3) TABLE ACCESS BY GLOBAL INDEX ROWID COMPAS.INDIVIDUAL [Analyzed]
Blocks: 164,566 Est. Rows: 11,538 of 7,411,916 Cost: 11,346
(2) NON-UNIQUE INDEX RANGE SCAN COMPAS.IDX_INDIVIDUAL_01 [Analyzed]
Est. Rows: 11,538 Cost: 77
(7) PARTITION RANGE ALL
(6) HASH JOIN
Est. Rows: 7,415,264 Cost: 7,958
(4) UNIQUE INDEX FAST FULL SCAN COMPAS.PK_HOUSEHOLD [Analyzed]
Est. Rows: 5,744,628 Cost: 1,069
(5) NON-UNIQUE INDEX FAST FULL SCAN COMPAS.HHLD_MEMB_LOCAL_02 [Analyzed]
Est. Rows: 7,415,264 Cost: 1,869
Followup June 6, 2005 - 6pm Central time zone:
compare the tkprof with the autotrace (you compare). are the estimated cardinalities (autotrace)
near reality (tkprof)
Index not used
June 7, 2005 - 9am Central time zone
Reviewer: reader from USA
Tom,
The tkprof without hint
SELECT DISTINCT (h.household_id)
FROM household h,
individual i,
household_member hm
WHERE h.household_id = hm.household_id
AND i.individual_id = hm.individual_id
AND i.last_name LIKE 'L%'
AND i.first_name LIKE 'R%'
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 1938 70.98 212.16 117549 213934 0 29054
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 1940 71.00 212.18 117549 213934 0 29054
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 5
Rows Row Source Operation
------- ---------------------------------------------------
29054 SORT UNIQUE
29559 HASH JOIN
29536 PARTITION RANGE ALL PARTITION: 1 10
29536 TABLE ACCESS FULL INDIVIDUAL PARTITION: 1 10
7422325 PARTITION RANGE ALL PARTITION: 1 10
7422325 HASH JOIN
5752931 INDEX FAST FULL SCAN PK_HOUSEHOLD PARTITION: 1 10 (object id 34674)
7422325 INDEX FAST FULL SCAN HHLD_MEMB_LOCAL_02 PARTITION: 1 10 (object id 34657)
********************************************************************************
select file#
from
file$ where ts#=:1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 6 0.01 0.00 0 0 0 0
Execute 6 0.00 0.00 0 0 0 0
Fetch 138 0.01 0.00 0 276 0 132
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 150 0.02 0.00 0 276 0 132
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: SYS (recursive depth: 1)
Rows Row Source Operation
------- ---------------------------------------------------
22 TABLE ACCESS BY INDEX ROWID FILE$
22 INDEX RANGE SCAN I_FILE2 (object id 42)
********************************************************************************
Tkprof with hint
SELECT /*+ INDEX( i IDX_INDIVIDUAL_01) */ DISTINCT (h.household_id)
FROM household h,
individual i,
household_member hm
WHERE h.household_id = hm.household_id
AND i.individual_id = hm.individual_id
AND i.last_name LIKE 'L%'
AND i.first_name LIKE 'R%'
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 1938 37.07 44.16 8133 80956 0 29054
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 1940 37.08 44.17 8133 80956 0 29054
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 5
Rows Row Source Operation
------- ---------------------------------------------------
29054 SORT UNIQUE
29559 HASH JOIN
29536 TABLE ACCESS BY GLOBAL INDEX ROWID INDIVIDUAL PARTITION: ROW LOCATION ROW LOCATION
29536 INDEX RANGE SCAN IDX_INDIVIDUAL_01 (object id 34747)
7422328 PARTITION RANGE ALL PARTITION: 1 10
7422328 HASH JOIN
5752932 INDEX FAST FULL SCAN PK_HOUSEHOLD PARTITION: 1 10 (object id 34674)
7422328 INDEX FAST FULL SCAN HHLD_MEMB_LOCAL_02 PARTITION: 1 10 (object id 34657)
********************************************************************************
select file#
from
file$ where ts#=:1
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 138 0.01 0.00 0 276 0 132
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 150 0.01 0.00 0 276 0 132
Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: SYS (recursive depth: 1)
Rows Row Source Operation
------- ---------------------------------------------------
22 TABLE ACCESS BY INDEX ROWID FILE$
22 INDEX RANGE SCAN I_FILE2 (object id 42)
The plan with hint is much better than paln without hint, wondering why not using index.
Followup June 7, 2005 - 12pm Central time zone:
I asked you to compare the GUESS (autotrace) to REALITY (tkprof)
this is not the result of you comparing the GUESS to REALITY. Are the guessed cardinalities even
close to what really happens.
eg: are the statistics accurate, complete, up to date and are they giving the optimizer the right
"information"
Index not used
June 7, 2005 - 2pm Central time zone
Reviewer: reader from USA
Tom,
Without hint the Autotrace estimate total Rows: 11,538 of 7,411,916 from INDIVIDUAL table full scan
the estimate for the total records in the table is accurate.
The tkprof got the 29536 rows from the INDIVIDUAL table full scan.
With the hint autotrace estimate total Rows: 11,538 of 7,411,916 Cost: 11,346 from NON-UNIQUE
INDEX RANGE SCAN COMPAS.IDX_INDIVIDUAL_01
The tkprof got the 29536 rows from the INDEX RANGE SCAN IDX_INDIVIDUAL_01
We are not gathereing the histograms (FACTS) on the table.
The statistics are accurate and complete. The same plan even after the following run.
BEGIN
dbms_stats.gather_schema_stats(
ownname => 'COMPAS',
cascade => TRUE,
estimate_percent => 25,
degree => 5,
method_opt => 'FOR ALL COLUMNS SIZE 1', -- no histograms only col stats
granularity => 'ALL'
);
END;
Followup June 7, 2005 - 2pm Central time zone:
any special optimizer parameters set?
index not used
June 7, 2005 - 4pm Central time zone
Reviewer: reader from USA
No not a single one,
it is 9.2.0.6 database
Followup June 7, 2005 - 6pm Central time zone:
have you gathered system statistics, so the database has a better understand of the characteristics
of your system (anything to avoid setting a parameter)
unclear about ...
June 7, 2005 - 8pm Central time zone
Reviewer: Gabe
Regarding these last posts
index not getting picked up aside, I would be curious to know:
1. Whats the definition of that index?
2. household_member seems to be the intersection table between household and individual
assuming the FK constraints are in place
why join with household at all? One can get the
household_ids from household_member
3. As a side note
what exactly is the use of getting a list of 30K ids?
4. Would a query like:
select individual_id from individual i
where i.last_name like 'L%'
and i.first_name like 'R%'
pick that index? That should bring back 30K out of 7.5M rows.
5. Would rewriting the query as:
select
from household_member where exists (
)
make any difference?
unclear
June 7, 2005 - 10pm Central time zone
Reviewer: reader from USA
Gabe,
The quey is the subquery of the big SQL.
SELECT LPAD (TRIM (tps.membership_number), 11, '0') member_num,
tps.account_individuals NAME, tps.date_of_births bday, tps.ages age,
tps.address address, tps.phone_numbers phone_num,
TO_CHAR (tps.household_id) hhld_id
FROM individual i,
(SELECT
pkg_person_search.fn_get_current_member_num (h1.household_id) membership_number,
pkg_person_search.fn_get_household_members (h1.household_id) account_individuals,
pkg_person_search.fn_get_hhld_mem_birth_date (h1.household_id) date_of_births,
pkg_person_search.fn_get_hhld_mem_age (h1.household_id) ages,
pkg_person_search.fn_get_household_address (h1.household_id) address,
pkg_person_search.fn_get_household_phone_nums (h1.household_id) phone_numbers,
h1.household_id household_id,
pkg_person_search.fn_get_result_order_indv_id (h1.household_id) sort_individual_id
FROM household h1
WHERE h1.household_id IN (
SELECT household_id
FROM (SELECT /*+ INDEX( i IDX_INDIVIDUAL_01) */ DISTINCT (h.household_id)
FROM household h,
individual i,
household_member hm
WHERE h.household_id = hm.household_id
AND i.individual_id = hm.individual_id
AND i.last_name LIKE 'L%'
AND i.first_name LIKE 'R%')
WHERE ROWNUM < 253)) tps
WHERE tps.sort_individual_id = i.individual_id
ORDER BY i.last_name, i.first_name
If I remove the Household reference still the plan is same not using index. The system stats is not
gathered for the database and not sure the impact of doing so, as this is production OLTP system.
SQL> SELECT DISTINCT (hm.household_id)
2 FROM individual i,
3 household_member hm
4 WHERE i.individual_id = hm.individual_id
5 AND i.last_name LIKE 'L%'
6 AND i.first_name LIKE 'R%';
29061 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=12666 Card=11541 Byt
es=484722)
1 0 SORT (UNIQUE) (Cost=12666 Card=11541 Bytes=484722)
2 1 HASH JOIN (Cost=12580 Card=11541 Bytes=484722)
3 2 PARTITION RANGE (ALL)
4 3 TABLE ACCESS (FULL) OF 'INDIVIDUAL' (Cost=10031 Card
=11538 Bytes=288450)
5 2 PARTITION RANGE (ALL)
6 5 INDEX (FAST FULL SCAN) OF 'HHLD_MEMB_LOCAL_02' (NON-
UNIQUE) (Cost=1869 Card=7415264 Bytes=126059488)
Followup June 8, 2005 - 8am Central time zone:
WHERE h1.household_id IN (
SELECT household_id
FROM (SELECT /*+ INDEX( i IDX_INDIVIDUAL_01) */ DISTINCT (h.household_id)
FROM household h,
individual i,
household_member hm
WHERE h.household_id = hm.household_id
AND i.individual_id = hm.individual_id
AND i.last_name LIKE 'L%'
AND i.first_name LIKE 'R%')
WHERE ROWNUM < 253)
that is a random data generator??
you take the first random 253 holdhold ids?? that -- that seems "strange"
but seems that household shouldn't even be in there doesn't it? I mean, just household_member is
more than sufficient to get the household_id?
still unclear ...
June 7, 2005 - 11pm Central time zone
Reviewer: Gabe
...
what about my #1 and #4?
6. OLTP? ... what are those tables partitioned by?
7. It seems a bit wasteful to have all that big join only to throw away all but 252 households. I
don't think that rownum can be pushed up because of the DISTINCT ... may work though if you rewrite
as I suggested in #5 with "select where exists"
If #4 does a FS you have a problem right there ... if it works OK then you can force that part to
somehow materialize as the very first step in the overall execution ... essentially getting the big
predicates to work first ... and then there is the rownum (!?!?!) would be nice to have this one
kick in early on.
PS. I don't think you should give Tom bits and pieces of a query. At least mention it.
Followup June 8, 2005 - 8am Central time zone:
I agree with the PS, this changes lots of thoughts...

June 8, 2005 - 9am Central time zone
Reviewer: reader from USA
Thanks Gabe and Tom for your suggestions. Here is my input.
#1 def. of index
CREATE INDEX compas.idx_individual_01 ON compas.individual
(
last_name ASC,
first_name ASC
)
PCTFREE 10
TABLESPACE ts_individual_01_x
/
#4 won't work as we need household_id and it is not available in individual table
#6 individual table partitioned by individual_id, all other tables partitioned by household_id
data is well distributed across the partitions
#7 I am sorry, I didn't get the #7, "I don't think that rownum can be pushed up because of the
DISTINCT" could you please elaborate little bit.
PS. I thought we had problem with subquery so instead of giving the big one I had just
mentioned the subquery. But query mentioned in the last note was full query, same as following.
SELECT LPAD (TRIM (tps.membership_number), 11, '0') member_num,
tps.account_individuals NAME, tps.date_of_births bday, tps.ages age,
tps.address address, tps.phone_numbers phone_num,
TO_CHAR (tps.household_id) hhld_id
FROM individual i,
(SELECT pkg_person_search.fn_get_current_member_num
(h1.household_id)
membership_number,
pkg_person_search.fn_get_household_members
(h1.household_id)
account_individuals,
pkg_person_search.fn_get_hhld_mem_birth_date
(h1.household_id)
date_of_births,
pkg_person_search.fn_get_hhld_mem_age (h1.household_id) ages,
pkg_person_search.fn_get_household_address
(h1.household_id)
address,
pkg_person_search.fn_get_household_phone_nums
(h1.household_id)
phone_numbers,
h1.household_id household_id,
pkg_person_search.fn_get_result_order_indv_id
(h1.household_id)
sort_individual_id
FROM household h1
WHERE h1.household_id IN (
SELECT household_id
FROM (SELECT /*+ INDEX( i IDX_INDIVIDUAL_01) */
DISTINCT (h.household_id)
FROM household h,
individual i,
household_member hm
WHERE h.household_id = hm.household_id
AND i.individual_id = hm.individual_id
AND i.last_name LIKE 'L%'
AND i.first_name LIKE 'R%')
WHERE ROWNUM < 253)) tps
WHERE tps.sort_individual_id = i.individual_id
ORDER BY i.last_name, i.first_name;

June 8, 2005 - 9am Central time zone
Reviewer: reader from USA
Just add the following,
Yes household_member is more than sufficient to get the household_id.
Followup June 8, 2005 - 10am Central time zone:
do that.
FROM household h1
WHERE h1.household_id IN (
SELECT household_id
FROM (SELECT DISTINCT (h.household_id)
FROM household h
where individual_id in
(select individual_id
from individual
where last_name LIKE
'L%'
AND first_name LIKE 'R%')
)
WHERE ROWNUM < 253)) tps
still seems "random", that distinct makes you wait till they are all retrieved, then you distinct
them and just keep the first couple random ones.
DISTINCT
June 8, 2005 - 11am Central time zone
Reviewer: reader from USA
Thanks a lot,
We need to present only 253 records to the users, DISTINCT is requird to remove duplicates from the
household_member table. Is there any way we can avoid it?
Followup June 8, 2005 - 11am Central time zone:
I understand why the distinct is needed (that comes from the model), but it just seems so very
strange to get 253 'random' ones.
tomorrow, same data -- different results are possible, just due to a change in the plan.
i would definitely LOSE all of the plsql functions -- 100%
but I bet if you rip this query apart, you could use your knowledge of the data to get it to a two
table join between individual and household.
You find households based on individuals (but lost the individual data) and join that back to
individuals again.... I really think there is room for improvement there, but you and your
knowledge of the data model, what the plsql functions do -- they are what is needed.

June 8, 2005 - 12pm Central time zone
Reviewer: reader from USA
Thanks,
The PL/SQL function is nothing but the small business logic to get the more detail about the
household, for example the following function called in that query,
FUNCTION fn_get_current_member_num (p_HouseholdID IN NUMBER)
RETURN VARCHAR2
IS
v_MemNum VARCHAR2(11);
BEGIN
SELECT hp.membership_number || '-' || hp.association_id
INTO v_MemNum
FROM household_profile hp
WHERE hp.household_id = p_HouseholdID
AND TRUNC (SYSDATE) BETWEEN hp.hhold_profile_start_date AND hp.hhold_profile_stop_date
-- AND SYSDATE BETWEEN hp.hhold_profile_start_date AND NVL(hp.hhold_profile_stop_date,SYSDATE)
-- AND NVL(hp.delete_ind,'N') = 'N';
AND hp.delete_ind = 'N';
RETURN v_MemNum;
END fn_get_current_member_num;
Followup June 8, 2005 - 1pm Central time zone:
drop them, use scalar subqueries.
select ...., (select ht.membership_number || .... ), ( select .... )
from ...

June 8, 2005 - 1pm Central time zone
Reviewer: reader from USA
That will to reduce the context switching right? Just curious what other benefits?
Thanks a lot for your help and time
Followup June 8, 2005 - 1pm Central time zone:
context switch gone.
procedural code = slower than just letting sql do it.
possible scalar subquery caching.
opportunity to take 1 or 2 or 3 of these and turn them into a single scalar subquery (rather than
hit table T 5 times, just hit it once).

June 8, 2005 - 2pm Central time zone
Reviewer: reader from USA
Great, I will do it, Thanks a lot
to ?reader? ...
June 8, 2005 - 2pm Central time zone
Reviewer: Gabe
If I could say few things here (one question for Tom in there)
A. The partitioning is likely not helping here at all
quite the opposite really. I think one
should do a post-mortem to figure out if partitioning (and the way it is implemented) is doing more
damage than good. Less than 10M households and individuals are still manageable without partitions.
B. The PL/SQL could be dealt with
but after resolving the inner part that deals with bringing the
252 households. The options here are: scalar subqueries (as already suggested) or one pl/sql call
returning an object with all those elements or even straight joins
C. It is not clear to me if a two table join between household with individual would suffice
the
individual_id returned by the pkg_person_search.fn_get_result_order_indv_id call may not
necessarily be one extracted by the innermost query (that is, may not be one of the L%andR%
people). But if possible, yes, by all means have the smallest number of tables involved in the
query.
D. There still is the issue of that query #4 (now appearing in the rewrite suggested by Tom)
I
know you need the household_id
but my point was that, if that query didnt pick the index then
there likely is something wrong in there (statistics, histograms
something). A plan using an
index to bring 30K out of the 7.5M records seems highly desirable. Giving that we now know the
index definition
Tom, do you think adding individual_id as the third column to that index would
make sense?
There is also the option of an FBI on:
(substr(last_name,1,1), first_name,1,1), individual_id)
plus the rewriting of course. But maybe thats too much for now.
E. My rumblings on #7 about rewriting the inner part with an exists had to do with getting that
rownum predicate in as soon as possible
the drawback is that it requires touching household
that is, assuming household(household_id) is a PK, something like:
select h.household_id
from household h
where exists ( select null
from household_member hm
,individual i
where hm.household_id = h.household_id
and hm.individual_id = i.individual_id
and i.last_name like 'L%'
and i.first_name like 'R%'
)
and rownum < 253
In retrospect, the extra trip to household may do more damage
just another option. Getting an
efficient filtering of individuals by name as the very first step seems to be the most promising
option.
I'll just leave it here.
Cheers.
Followup June 8, 2005 - 4pm Central time zone:
(c) correct, that is why I put the ball right back in their court - using their knowledge of the
model, this is probably something that can be simplified. we don't have the details.
Single row Join
June 8, 2005 - 3pm Central time zone
Reviewer: DBA from Riverside, CA
Hi Tom,
Here is my sql -
select A.*
from TableA A,
security_table X
where X.agency_ind = 'AgencyA'
and X.operid = USER
Here, security_table has agency_ind+operid as a unique key and so, it will fetch only one row,
satisfy the condition and return the data from TableA.
This works fine and I get the data extremely fast. But, my concern is, if I do the same with a
view, the sql dies. Here is what I am doing -
If I run -
select A.*
from ViewA A
- it runs very fast.
If I run -
select A.*
from ViewA A,
security_table X
where X.agency_ind = 'AgencyA'
and X.operid = USER
- it goes forever though security_table fetches a single row.
Is it because of cartesian product with single row? Pl explain.
Thanks
Followup June 8, 2005 - 9pm Central time zone:
what does the plan look like
I cannot tell because I don't have the schema, or the data.
many small SQLs or 1 big SQL
June 13, 2005 - 10am Central time zone
Reviewer: A reader
Tom,
As a general practise, within a pl/sql procedure/function is it better (performance wise) to write
5-6 small SQLs or write a single big SQL query?
What i mean is which following method is better?
------------------------------------------------
select col1 into var1
from tab1
where <>;
select col2 into var2
from tab2
where tab2.<> = var1;
select col3 into var3
from tab3
where tab3.<> = var2;
select col4 into var4
from tab4
where tab4.<> = var3;
------------------------------------------------
select col1,col2,col3,col4
from tab1,tab2,tab3,tab4
where <>;
------------------------------------------------
Please comment.
Followup June 13, 2005 - 11am Central time zone:
do as much in a single sql statement as you can and erase as much procedural code as possible.
this is really usefull
June 21, 2005 - 8am Central time zone
Reviewer: Shyam from USA
I have problem with redo log buffer.
when i query the datbase
SQL> Select name, value from v$sysstat
2 Where name = 'redo log space requests';
NAME VALUE
----- ------
redo log 419
space requests
But actually this value should be ideally be zero.How can i fix this i had made Log_buffer=1M
Any other suggestions
Regards
Followup June 21, 2005 - 4pm Central time zone:
419 is the number since the instance is up and running.
419 is a small number if you ask me. Don't you think it is small?
But, this is not a wait for space in the log buffer (so playing with that, won't affect this).
This is not a wait for lgwr to finish a write (that is log file sync).
This is something you might see at a log switch, if we cannot switch because of checkpoint not
complete, or archival required.
So, do you see any of those messags in your alert log?
query tune
June 21, 2005 - 2pm Central time zone
Reviewer: whizkid from APAC
hi tom,
would be grateful if you help once again.. stuck up with these two queries
SELECT (-1) * SUM(A.POSTING_AMT)
FROM
ACC_POSTINGS A , BJAZ_ACX_TCODE B , AC_INTERNAL_ACCOUNTS C WHERE A.BATCH_ID =
B.BATCH_ID AND A.EVENT_NO = B.EVENT_NO AND A.POSTING_NO = B.POSTING_NO AND
A.INTERNAL_ACCOUNT_ID = C.INTERNAL_ACCOUNT_ID AND C.SUBFACTOR_1_VAL = :B3
AND C.ACCOUNT_CATEGORY_CODE = '3110111105' AND B.RECEIPT_NO = :B2 AND
B.COLLECTION_NO = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 18 0.00 0.00 0 0 0 0
Execute 24 0.01 0.00 0 0 0 0
Fetch 24 65.60 64.08 1 10844907 0 24
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 66 65.61 64.09 1 10844907 0 24
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 5386 (recursive depth: 1)
Rows Row Source Operation
------- ---------------------------------------------------
1 SORT AGGREGATE
74594 NESTED LOOPS
74595 NESTED LOOPS
74595 TABLE ACCESS BY GLOBAL INDEX ROWID BJAZ_ACX_TCODE PARTITION: ROW LOCATION ROW LOCATION
74595 INDEX RANGE SCAN RCPT_COLL_IDX (object id 67452)
74595 PARTITION RANGE ITERATOR PARTITION: KEY KEY
74595 TABLE ACCESS BY LOCAL INDEX ROWID ACC_POSTINGS PARTITION: KEY KEY
74595 INDEX RANGE SCAN ACC_P_PK PARTITION: KEY KEY (object id 67376)
74594 INDEX RANGE SCAN IDX_SUB1_ACCTCD_INTAC (object id 97599)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
global cache cr request 34 0.00 0.01
db file sequential read 1 0.00 0.00
and
SELECT INTERNAL_ACCOUNT_ID
FROM
AC_INTERNAL_ACCOUNTS WHERE SUBFACTOR_1_VAL = TO_CHAR(NVL(:B1,0)) AND
ACCOUNT_CATEGORY_CODE ='3110111105' FOR UPDATE NOWAIT
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 18 0.00 0.00 0 0 0 0
Execute 449 164.08 160.19 0 4615714 461 0
Fetch 449 164.63 160.59 0 4615712 0 449
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 916 328.71 320.78 0 9231426 461 449
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 5386 (recursive depth: 1)
Rows Row Source Operation
------- ---------------------------------------------------
1 FOR UPDATE
2 CONCATENATION
0 FILTER
0 TABLE ACCESS BY INDEX ROWID AC_INTERNAL_ACCOUNTS
0 INDEX RANGE SCAN IDX_AC_INT_ACC_SUBVAL1 (object id 96138)
2 FILTER
2 TABLE ACCESS BY INDEX ROWID AC_INTERNAL_ACCOUNTS
505688 INDEX RANGE SCAN IDX_AC_INT_ACC_SUBVAL1 (object id 96138)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
global cache cr request 1 0.00 0.00
global cache null to x 1 0.00 0.00
the table ac_internal_accounts has the following indexes
SQL> l
1 select table_name, index_name, column_name, column_position
2 from dba_ind_columns where table_name = 'AC_INTERNAL_ACCOUNTS'
3* order by 2, 4
SQL> /
TABLE_NAME INDEX_NAME COLUMN_NAME COLUMN_POSITION
------------------------------ ------------------------- ------------------------- ---------------
AC_INTERNAL_ACCOUNTS AC_IA_PK INTERNAL_ACCOUNT_ID 1
AC_INTERNAL_ACCOUNTS AC_IA_UK1 COMPANY_ID 1
AC_INTERNAL_ACCOUNTS AC_IA_UK1 SWIFT_CODE 2
AC_INTERNAL_ACCOUNTS AC_IA_UK1 ACCOUNT_CATEGORY_CODE 3
AC_INTERNAL_ACCOUNTS AC_IA_UK1 SUBFACTOR_1_VAL 4
AC_INTERNAL_ACCOUNTS AC_IA_UK1 SUBFACTOR_2_VAL 5
AC_INTERNAL_ACCOUNTS AC_IA_UK1 SUBFACTOR_3_VAL 6
AC_INTERNAL_ACCOUNTS AC_IA_UK1 SUBFACTOR_4_VAL 7
AC_INTERNAL_ACCOUNTS IDX_AC_INT_ACC_SUBVAL1 SUBFACTOR_1_VAL 1
AC_INTERNAL_ACCOUNTS IDX_AC_INT_ACC_SUBVAL2 SUBFACTOR_2_VAL 1
AC_INTERNAL_ACCOUNTS IDX_SUB1_ACCTCD_INTAC INTERNAL_ACCOUNT_ID 1
AC_INTERNAL_ACCOUNTS IDX_SUB1_ACCTCD_INTAC ACCOUNT_CATEGORY_CODE 2
AC_INTERNAL_ACCOUNTS IDX_SUB1_ACCTCD_INTAC SUBFACTOR_1_VAL 3
AC_INTERNAL_ACCOUNTS IND_AC_INT_ACC_CATCODE ACCOUNT_CATEGORY_CODE 1
what new index could i add to speed up the response for these queries?
Followup June 21, 2005 - 5pm Central time zone:
2 TABLE ACCESS BY INDEX ROWID AC_INTERNAL_ACCOUNTS
505688 INDEX RANGE SCAN IDX_AC_INT_ACC_SUBVAL1 (object id 96138
that should be a red flag. Look at that index and you figure out what you can add to it so that
you do not get 505,688 FALSE POSITIVES that result in only 2 rows from the table.
eg:
you have a predicate like "where x = :x and y = :y and z = :z"
you have a index on X only.
You run the query with :x = 100 and :y = 1. There are 505,688 rows with x = 100, only 2 of them
have Y = 1. You needed to have indexed (x,y) not just x for this query.

June 21, 2005 - 7pm Central time zone
Reviewer: A reader
Tom,
Why is the parse 18 times for the query?
Thanks.
Followup June 21, 2005 - 11pm Central time zone:
if this is plsql, then it ran out of cursor cache space and had to be reparsed.
9205 and above - session_cached_cursors controls the size of the cache, before that open_cursors
did.

June 21, 2005 - 10pm Central time zone
Reviewer: Partha from Singapore
Whizkid,
"what new index could i add to speed up the response for these queries? "
You're kidding right ?.
You seem to be having too many indexes (or is it indices?) in that table. It's not necessary to
have indexes on all possible combination. Is this is a OLTP system or a Warehouse system ? If it is
an OLTP, did you check out what impact it will have when you have to do inserts or updates on that
table.
On first glance, looks like "AC_IA_UK1" index has all possible columns. Try to put the restrictive
clauses ( company_id, swift_code) if possible / appropriate and allow it to use this index. You
seriously need to rethink whether you need all those other exta indexes (for example
idx_ac_int_acc_subval1, 2, etc).
And regarding parsing 18 times, it could be soft parse, when being called repetitively by opening
and closing cursor (not sure).
parse count
June 22, 2005 - 6am Central time zone
Reviewer: whizkid from APAC
hi tom,
this is being called from PL/SQL and we are on 9205. So does this mean that the
session_cached_cursors value is too small? We have set it to 300. This is actually a batch job that
gets run once in a while. So is it ok or should I increase the value?
tom, on concatenated column indexes, how exactly should we decide on the order of the columns? does
it also matter? suppose :x returns 550000 rows and :y returns 2 rows. show should the index be on
(x, y) or (y, x) or any order and no difference. i was under the impression that the leading edge
column should have minimum number of rows?
Partha,
We have an OLTP system and have monitored for inserts / updates. there are no problems during that.
but we have selects on this table using all possible permutations and this being a big table, it
takes a lot of time to get the results.
Followup June 22, 2005 - 7am Central time zone:
look at this holistically...
how much time did you spend in this case parsing? it doesn't seem to be an issue here.
do you have access to Expert One on One Oracle? I cover the order of columns in the index. If the
only query is "where x = :x and y = :y", the order of the columns isn't really relevant.
You are quoting a big big myth that the leading edge should be "most selective"
Re: parse count
June 22, 2005 - 7am Central time zone
Reviewer: whizkid from APAC
well.. not that i look at it, it does not take any time in parsing at all.. guess I got an attack
of CTD. :-)
I do not have your Expert One on One Oracle. :-( Is it possible for you to paste the appropriate
extract here?
thanks.
Followup June 22, 2005 - 7am Central time zone:
basically, you choose the order of columns in the index based on the QUESTIONS you ask.
where x = :x and y = :y
where x > :x and y = :y
that would lead me to want an index on (y,x), REGARDLESS of the distinctness of either. that index
would be best for both.
where x = :x
where x = :x and y = :y
where x = :x and y = :y and z = :z
would lead me to want an index in (x,y,z), REGARDLESS of the distinctness of x,y,z. That single
index would be useful for all three
you look at the QUESTIONS asked and based on that pick the index of choice. The book just showed
how the index on (x,y) or (y,x) used with the predicate where x = :x and y = :y performance
identically regardless of the distinctness of either.
performance issue when loading data
June 22, 2005 - 1pm Central time zone
Reviewer: shyam
Tom when loading data for evry 10 mins it is generating 1.2 GB of log.over all for loading it is
taking 12 hours to load.
pl let me know what should i do to decrease the loading time.loading is not done with exp imp or
sqlloader i is done using .net procedure.
Followup June 23, 2005 - 1pm Central time zone:
like I said elsewhere for this question
you are probably doing this slow by slow (row by row) and not really taking advantage of any
database feature whatsoever
but given the inputs we have here, that is all anyone could say.
1.2 gig of log is pretty small for a 12 hour load.

June 27, 2005 - 2pm Central time zone
Reviewer: A reader
Hi Tom,
We have a situation like this:
Table t1 has some indexes.
The query was intially using all the indexes.
But, later we had copied that data into a
new table and built all the indexes. Since then,
the query has stopped using indexes. Could you guide
us what could be the reason? We have sufficient amount
of data for CBO to use indexes.
Thanks.
Followup June 27, 2005 - 2pm Central time zone:
likely culprit is the clustering factor is very different after the reorg of the table.
but, are you sure indexes would be your "fast=true" switch here, suggest you hint it for a test --
indexes are not always the best thing.

June 29, 2005 - 5am Central time zone
|