Oracle SQL Query Tuning Hints
Oracle SQL Query Tuning Hints
Oracle SQL Query Tuning Hints
WHERE Clause
Try to avoid operations on database objects referenced in the WHERE clause.
HAVING Clause
The HAVING clause filters selected rows only after all rows have been fetched. Using a WHERE
clause helps reduce overheads in sorting, summing, etc. HAVING clauses should only be used
when columns with summary operations applied to them are restricted by the clause.
Combined Subqueries
Minimize the number of table lookups (subquery blocks) in queries, particularly if your
statements include subquery SELECTs or multicolumn UPDATEs.
SELECT ename
FROM emp E
WHERE EXISTS (SELECT 'X'
FROM dept
WHERE deptno = E.deptno
AND dname = 'ACCOUNTING');
SELECT ename
FROM emp E
WHERE deptno IN (SELECT deptno
FROM dept
WHERE deptno = E.deptno
AND dname = 'ACCOUNTING');
SELECT ename
FROM dept D, emp E
WHERE E.deptno = D.deptno
AND D.dname = 'ACCOUNTING';
DISTINCT
Avoid joins that require the DISTINCT qualifier on the SELECT list in queries which are
used to determine information at the owner end of a one-to-many relationship. The
DISTINCT operator causes Oracle to fetch all rows satisfying the table join and then sort
and filter out duplicate values. EXISTS is a faster alternative, because the Oracle optimize
realizes when the subquery has been satisfied once, there is no need to proceed further
and the next matching row can be fetched.
DECODE
Consider using DECODE to avoid having to scan the same rows repetitively or join the
same table repetitively. Note, DECODE is not necessarily faster as it depends on your data
and the complexity of the resulting query. Also, using DECODE requires you to change
your code when new values are allowed in the field.
SELECT COUNT(*)
FROM emp
WHERE status = 'Y'
AND ename LIKE 'SMITH%';
----------
SELECT COUNT(*)
FROM emp
WHERE status = 'N'
AND ename LIKE 'SMITH%';
SELECT COUNT(DECODE(status, 'Y', 'X', NULL)) Y_count,
COUNT(DECODE(status, 'N', 'X', NULL)) N_count
FROM emp
WHERE ename LIKE 'SMITH%';
Anti Joins
An anti-join is used to return rows from a table that that are present in another table. It might b
used for example between DEPT and EMP to return only those rows in DEPT that didn't join to
anything in EMP;
SELECT *
FROM dept
WHERE deptno NOT IN (SELECT deptno FROM EMP);
SELECT dept.*
FROM dept, emp
WHERE dept.deptno = emp.deptno (+)
AND emp.ROWID IS NULL;
SELECT *
FROM dept
WHERE NOT EXISTS (SELECT NULL FROM emp WHERE emp.deptno = dept.deptno);
Full
Outer Joins
Normally, an outer join of table A to table B would return every record in table A, and if it had a
mate in table B, that would be returned as well. Every row in table A would be output, but some
rows of table B might not appear in the result set. A full outer join would return ebery row in
table A, as well as every row in table B. The syntax for a full outer join is new in Oracle 9i, but it
is a syntactic convenience, it is possible to produce full outer joins sets using conventional SQL.
OK, so why use the complicated select in the first place? Why not just create the view? Well, on
good reason is that creating a view gives you another database object to maintain, and adds
more complexity to your system. By placing the view "inside" your main select, you have all of
the code needed to support the query in one place.
Overview
The inline view is a construct in Oracle SQL where you can place a query in the SQL FROM,
clause, just as if the query was a table name.
OK, so why use the complicated select in the first place? Why not just create the view?
Well, one good reason is that creating a view gives you another database object to maintain,
and adds more complexity to your system. By placing the view "inside" your main select,
you have all of the code needed to support the query in one place.
SELECT a
FROM table
WHERE id = :id
AND b = (SELECT MAX (b)
FROM table
WHERE id = :id)
... it can be worth to check if an inline view, instead of the subquery will be faster.
Index created.
Table analyzed.
VARIABLE b1 NUMBER
exec :b1 := 10
SELECT max(height)
from test
WHERE id = :b1
AND acc_date = (SELECT MAX(acc_date)
FROM test
WHERE id = :b1);
MAX(HEIGHT)
-----------
1480603530
Elapsed: 00:00:00.12
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=17)
1 0 SORT (AGGREGATE)
2 1 INDEX (RANGE SCAN) OF 'TEST_IDX' (NON-UNIQUE) (Cost=2 Card=1
Bytes=17)
3 2 SORT (AGGREGATE)
4 3 FIRST ROW (Cost=2 Card=6 Bytes=60)
5 4 INDEX (RANGE SCAN (MIN/MAX)) OF 'TEST_IDX' (NON-UNIQUE)
(Cost=2 Card=1060)
VARIABLE b1 NUMBER
exec :b1 := 10
SELECT height
FROM (SELECT height
FROM test
WHERE id = :b1
ORDER BY id DESC, acc_date DESC, height DESC)
WHERE ROWNUM = 1;
HEIGHT
----------
1480603530
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=13)
1 0 COUNT (STOPKEY)
2 1 VIEW (Cost=2 Card=6 Bytes=78)
3 2 INDEX (RANGE SCAN DESCENDING) OF 'TEST_IDX' (NON-UNIQUE)
(Cost=2 Card=6 Bytes=102)
SELECT switch_time,rat_id
FROM tariff
WHERE effdate = (SELECT MAX(effdate)
FROM tariff
WHERE effdate <= TRUNC(:b1)
AND weekday = :b2
AND t_id = :b3)
AND TO_CHAR(switch_time,'HH24:MI') <= TO_CHAR(:b1,'HH24:MI')
AND weekday = :b2
AND t_id = :b3
ORDER BY TO_CHAR(switch_time,'HH24:MI') DESC
With Subquery
VARIABLE b1 VARCHAR2(19)
exec :b1 := '07.04.1999:13:30:31'
VARIABLE b2 NUMBER
exec :b2 := 2
VARIABLE b3 NUMBER
exec :b3 := 317
SWITCH_TI RAT_ID
--------- ----------
01-JAN-98 3
01-JAN-98 1
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=21)
1 0 SORT (ORDER BY) (Cost=4 Card=1 Bytes=21)
2 1 FILTER
3 2 TABLE ACCESS (FULL) OF 'TARIFF' (Cost=2 Card=1 Bytes=21)
4 3 SORT (AGGREGATE)
5 4 FILTER
6 5 INDEX (RANGE SCAN) OF 'PK_TARIFF' (UNIQUE) (Cost=2
Card=1 Bytes=12)
TKPROF:
VARIABLE b1 VARCHAR2(19)
exec :b1 := '07.04.2005:13:30:31'
VARIABLE b2 NUMBER
exec :b2 := 2
VARIABLE b3 NUMBER
exec :b3 := 317
SWITCH_TI RAT_ID
--------- ----------
01-JAN-98 3
01-JAN-98 1
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=22)
1 0 VIEW (Cost=4 Card=1 Bytes=22)
2 1 SORT (ORDER BY) (Cost=4 Card=1 Bytes=21)
3 2 FILTER
4 3 TABLE ACCESS (BY INDEX ROWID) OF 'TARIFF' (Cost=2 Card=1
Bytes=21)
5 4 INDEX (RANGE SCAN) OF 'PK_TARIFF' (UNIQUE) (Cost=2 Card=1)
TKPROF:
One of the limitations of hierarchical queries is that you cannot join to them. However, there
are often times you would like to join to them anyway. For instance, if the hierarchy table
only has surrogate keys, and you would like to display the real value. This tip shows how
you can use "Inline Views" to join tables to a hierarchical query.
ROWNUM ENAME
---------- ----------
1 SMITH
2 ALLEN
3 WARD
However, if you try to use a range it will not work. For example:
no rows selected
Using an Inline View to get around this limitation:
The main trick to this query is the "internal" select statement. This select statement in the
from clause, basically does a full query of the table, then returns the values (along with the
psuedo-column ROWNUM) to the "outside" query. The outside query can then operate on
the results of the internal query. In order to access the internal query's columns from the
external query, you need to give the internal query an alias ("t1" highlighted below): This
allows you to refer to the columns using the "t1" (highlighted below): Since "ROWNUM" is a
psuedo-column and therefore a reserved word, you need to alias that column in the internal
query in order to refer to it in the outside query:
select * from emp where ROWNUM <= 5 order by sal desc; /* WRONG! */
The users intention was most likely to get the the top-five paid people - a top-N query.
What the will get is five random records (the first five we happen to hit), sorted by salary. If
you use an inline view with the ORDER BY inside the inline view, you get the correct result.
select * from (select * from emp order by sal desc) where rownum <= 5;
SELECT *
FROM (SELECT a.*, ROWNUM rn
FROM (enter your query here) a
WHERE ROWNUM <= :MAX_ROW)
WHERE rn >= :MIN_ROW;
SELECT *
FROM (SELECT a.*, ROWNUM rn
FROM (SELECT * FROM emp) a
WHERE ROWNUM <= 6)
WHERE rn >= 2;