Oracle SQL Query Tuning Hints WHERE Clause

Try to avoid Given Query SELECT ename, hiredate, sal FROM emp WHERE SUBSTR(ename,1,3) = 'SCO'; VARIABLE name VARCHAR2(20) exec name := 'SCOTT' SELECT ename, hiredate, sal FROM emp WHERE ename = NVL (:name, ename); SELECT ename, hiredate, sal FROM emp WHERE TRUNC (hiredate) = TRUNC (SYSDATE); SELECT ename, hiredate, sal FROM emp WHERE ename || empno = 'SCOTT7788'; SELECT ename, hiredate, sal FROM emp WHERE sal + 3000 < 5000; SELECT ename, hiredate, sal FROM emp WHERE sal != 0; Alternative SELECT ename, hiredate, sal FROM emp WHERE ename LIKE 'SCO%'; VARIABLE name VARCHAR2(20) exec name := 'SCOTT' SELECT ename, hiredate, sal FROM emp WHERE ename LIKE NVL (:name, '%'); SELECT ename, hiredate, sal FROM emp WHERE hiredate BETWEEN TRUNC (SYSDATE) AND TRUNC (SYSDATE) + .99999; SELECT ename, hiredate, sal FROM emp WHERE ename = 'SCOTT AND empno = 7788; SELECT ename, hiredate, sal FROM emp WHERE sal < 2000; SELECT ename, hiredate, sal FROM emp WHERE sal > 0;

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. Given Query SELECT d.dname, AVG (e.sal) FROM emp e, dept d WHERE e.deptno = d.deptno GROUP BY d.dname HAVING dname != 'RESEAECH' AND dname != 'SALES'; Alternative SELECT d.dname, AVG (e.sal) FROM emp e, dept d WHERE e.deptno = d.deptno AND dname != 'RESEAECH' AND dname != 'SALES' GROUP BY d.dname;

.deptno. because the Oracle optimizer realizes when the subquery has been satisfied once. d.dname = 'ACCOUNTING'.deptno. Table Joins Consider the alternatives EXISTS. particularly if your statements include subquery SELECTs or multicolumn UPDATEs. emp E WHERE E. NOT IN. SELECT ename FROM emp E WHERE deptno IN (SELECT deptno FROM dept WHERE deptno = E. there is no need to proceed further and the next matching row can be fetched. SELECT ename FROM dept D. it depends on your data. None of these are consistently faster.deptno AND dname = 'ACCOUNTING').deptno = D. EXISTS is a faster alternative.comm) = (SELECT MAX (sal). 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.deptno).deptno.dname FROM dept D WHERE EXISTS (SELECT 'X' FROM emp E WHERE E. Alternative SELECT d. SELECT ename FROM emp E WHERE EXISTS (SELECT FROM WHERE AND 'X' dept deptno = E. EXISTS.deptno = E.deptno AND D.deptno dname = 'ACCOUNTING'). emp E WHERE D. Given Query SELECT DISTINCT d. d.Combined Subqueries Minimize the number of table lookups (subquery blocks) in queries. The DISTINCT operator causes Oracle to fetch all rows satisfying the table join and then sort and filter out duplicate values.dname FROM dept D. IN and table joins when doing multiple table joins. Separate Subqueries SELECT ename FROM emp WHERE sal = (SELECT MAX (sal) FROM lookup) AND comm = (SELECT MAX (comm) FROM lookup). Combined Subqueries SELECT ename FROM emp WHERE (sal. MAX(comm) FROM lookup).deptno = D.

the UNION ALL is much more efficient. The UNION clause forces all rows returned by each portion of the UNION to be sorted and merged and duplicates to be filtered before the first row is returned. DECODE is not necessarily faster as it depends on your data and the complexity of the resulting query. using DECODE requires you to change your code when new values are allowed in the field.deptno = emp. 'Y'.deptno (+) emp. SELECT * FROM dept WHERE deptno NOT IN (SELECT deptno FROM EMP). NULL)) N_count FROM emp WHERE ename LIKE 'SMITH%'.ROWID IS NULL. Anti Joins An anti-join is used to return rows from a table that that are present in another table. balance FROM debit WHERE trandate = '31-DEC-95' UNION ALL SELECT acct. emp dept. Also. UNION ALL SELECT acct.* dept. SELECT COUNT(DECODE(status. balance FROM debit WHERE trandate = '31-DEC-95' UNION SELECT acct. 'N'. ---------SELECT COUNT(*) FROM emp WHERE status = 'N' AND ename LIKE 'SMITH%'. balance FROM credit WHERE trandate = '31-DEC-95'. SELECT FROM WHERE AND dept.UNION ALL Consider whether a UNION ALL will suffice in place of a UNION. It might be used for example between DEPT and EMP to return only those rows in DEPT that didn't join to anything in EMP. Note. SELECT COUNT(*) FROM emp WHERE status = 'Y' AND ename LIKE 'SMITH%'. balance FROM credit WHERE trandate = '31-DEC-95'. or you don't care if duplicates are returned. DECODE Consider using DECODE to avoid having to scan the same rows repetitively or join the same table repetitively. merge or filter. If your tables are mutually exclusive (include no duplicate records). 'X'. NULL)) Y_count. 'X'. . A UNION ALL simply returns all rows including duplicates and does not have to perform any sort. COUNT(DECODE(status. UNION SELECT acct.

update emp set deptno = 9 where deptno = 10.deptno = dept. Every row in table A would be output.4. and adds more complexity to . A full outer join would return ebery row in table A.deptno) deptno.-----------SMITH 20 RESEARCH ALLEN 30 SALES WARD 30 SALES JONES 20 RESEARCH MARTIN 30 SALES BLAKE 30 SALES CLARK 9 SCOTT 20 RESEARCH KING 9 TURNER 30 SALES ADAMS 20 RESEARCH JAMES 30 SALES FORD 20 RESEARCH MILLER 9 10 ACCOUNTING 40 OPERATIONS Inline VIEWS The inline view is a construct in Oracle SQL where you can place a query in the SQL FROM. just as if the query was a table name.deptno). but it is a syntactic convenience. one good reason is that creating a view gives you another database object to maintain. that would be returned as well.deptno = dept.2. Conventional SQL SELECT empno.3.deptno.SELECT * FROM dept WHERE NOT EXISTS (SELECT NULL FROM emp WHERE emp. emp. EMPNO -----------------RESEARCH 7499 7521 7566 7654 7698 7782 7788 7839 7844 7876 7900 7902 7934 ENAME DEPTNO DNAME ---------. NULL FROM emp. dept. ename.---------7369 SMITH 20 ALLEN WARD JONES MARTIN BLAKE CLARK SCOTT KING TURNER ADAMS JAMES FORD MILLER 30 30 20 30 30 9 20 9 30 20 30 20 9 10 40 SALES SALES RESEARCH SALES SALES RESEARCH SALES RESEARCH SALES RESEARCH ACCOUNTING OPERATIONS New Syntax SELECT empno. ename.---------. dept WHERE emp. The syntax for a full outer join is new in Oracle 9i.deptno UNION ALL SELECT empno.3.deptno(+) AND dept.deptno(+) = dept.deptno. and if it had a mate in table B. but some rows of table B might not appear in the result set. Full Outer Joins Normally. dept WHERE emp.4. clause. EMPNO ---------7369 7499 7521 7566 7654 7698 7782 7788 7839 7844 7876 7900 7902 7934 ENAME DEPTNO DNAME ---------. NVL(dept. ename.deptno) ORDER BY 1. dname FROM emp. dname FROM emp FULL OUTER JOIN dept ON (emp.deptno.2.deptno IS NULL ORDER BY 1.deptno = dept.emp. an outer join of table A to table B would return every record in table A. so why use the complicated select in the first place? Why not just create the view? Well. it is possible to produce full outer joins sets using conventional SQL. as well as every row in table B. OK. commit.

you have all of the code needed to support the query in one place.. one good reason is that creating a view gives you another database object to maintain.VALUE(0. instead of the subquery will be faster.1000) FROM all_objects. height. acc_date. Example 1 (Replace Subquery for MAX) With Subquery CREATE TABLE test (id INT. ANALYZE TABLE test COMPUTE STATISTICS . By placing the view "inside" your main select... it can be worth to check if an inline view. 6357 rows created. acc_date) SELECT MOD(ROWNUM. CREATE INDEX test_idx on test (id. DBMS_RANDOM. just as if the query was a table name. INSERT INTO test (id. acc_date DATE). If you have a query as the following . Index created. and adds more complexity to your system.1000).RANDOM. clause. OK.your system. so why use the complicated select in the first place? Why not just create the view? Well. height). COMMIT. SELECT FROM WHERE AND a table id = :id b = (SELECT MAX (b) FROM table WHERE id = :id) . The Power of Inline Views Overview The inline view is a construct in Oracle SQL where you can place a query in the SQL FROM. SYSDATE-1000+DBMS_RANDOM. you have all of the code needed to support the query in one place. By placing the view "inside" your main select.. height INT.

00 0 0 0 0 2 2 0 0 0 -------.FOR TABLE FOR ALL INDEXES FOR ALL INDEXED COLUMNS.00 0.00 0.---------. VARIABLE b1 NUMBER exec :b1 := 10 ALTER SESSION SET TIMED_STATISTICS=TRUE.---------.00 0.00 0. alter session set sql_trace=true. alter session set timed_statistics=true.00 0.---------.---------.---------0. ALTER SESSION SET SQL_TRACE=TRUE. MAX(HEIGHT) ----------1480603530 Elapsed: 00:00:00.---------.---------.trc gek1_ora_16520.out explain=scott/tiger sort=exeela sys=no call count rows ------.---------0.--------------Parse 1 0 Execute 1 0 Fetch 2 1 ------.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) tkprof gek1_ora_16520.--------------total 4 cpu elapsed disk query current -------. Table analyzed. SELECT from WHERE AND max(height) test id = :b1 acc_date = (SELECT MAX(acc_date) FROM test WHERE id = :b1).00 0.00 0 4 0 .

---------.---------0.03 0.00 0.trc gek1_ora_16521.---------.--------------total 4 1 cpu elapsed disk query current height test id = :b1 id DESC.1 With Inline View VARIABLE b1 NUMBER exec :b1 := 10 SELECT height FROM (SELECT FROM WHERE ORDER BY WHERE ROWNUM = 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) tkprof gek1_ora_16521.---------.--------------Parse 1 0 Execute 1 0 Fetch 2 1 ------.---------.00 0. -------.00 2 0 0 41 0 2 0 0 0 -------. acc_date DESC.00 0.06 2 43 0 Example 2 (Replace Subquery for MAX) Original Query from a trace session: SELECT switch_time.rat_id FROM tariff WHERE effdate = (SELECT MAX(effdate) FROM tariff WHERE effdate <= TRUNC(:b1) . height DESC) 1.out explain=scott/tiger sort=exeela sys=no call count rows ------.---------.03 0.06 0.---------0.---------.

'HH24:MI') DESC. alter session set sql_trace=true.1999:13:30:31' VARIABLE b2 NUMBER exec :b2 := 2 VARIABLE b3 NUMBER exec :b3 := 317 SELECT switch_time.'DD. VARIABLE b1 VARCHAR2(19) exec :b1 := '07.MM.'HH24:MI') DESC With Subquery alter session set timed_statistics=true.YYYY:HH24:MI:SS').---------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: .'HH24:MI') weekday = :b2 t_id = :b3 BY TO_CHAR(switch_time.'HH24:MI') <= TO_CHAR(:b1.04.'HH24:MI') <= TO_CHAR(TO_DATE(:b1. SWITCH_TI RAT_ID --------. select value from v$parameter where name = 'user_dump_dest'.MM.AND AND AND ORDER AND weekday = :b2 AND t_id = :b3) TO_CHAR(switch_time.'DD.'HH24:MI') AND weekday = :b2 AND t_id = :b3 ORDER BY TO_CHAR(switch_time.YYYY:HH24:MI:SS')) AND weekday = :b2 AND T_ID = :b3) AND TO_CHAR(switch_time. rat_id FROM tariff WHERE effdate = (SELECT MAX(effdate) FROM tariff WHERE effdate <= TRUNC(TO_DATE(:b1.

---------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) .'HH24:MI') <= TO_CHAR(TO_DATE(:b1.01 0. SWITCH_TI RAT_ID --------. rat_id FROM tariff WHERE effdate <= TRUNC(TO_DATE(:b1.tkprof xyz.YYYY:HH24:MI:SS')) AND weekday = :b2 AND t_id = :b3 ORDER BY effdate DESC) WHERE TO_CHAR(switch_time.---------.'DD.trc xyz.---------.---------. rat_iD FROM (SELECT switch_time.00 0.---------0.YYYY:HH24:MI:SS').MM.---------.00 0 38 8 Misses in library cache during parse: 0 Optimizer goal: CHOOSE With Inline View VARIABLE b1 VARCHAR2(19) exec :b1 := '07.00 0.'DD.00 0.---------.--------------total 4 4 cpu elapsed disk query current -------.04.MM.---------.00 0 0 0 0 0 38 0 0 8 -------.2005:13:30:31' VARIABLE b2 NUMBER exec :b2 := 2 VARIABLE b3 NUMBER exec :b3 := 317 SELECT switch_time.'HH24:MI').01 0.---------0.out explain=user/pwd sort=exeela sys=no call count rows ------.--------------Parse 1 0 Execute 1 0 Fetch 2 4 ------.00 0.

and you would like to display the real value.---------.empno. ORA-01437: cannot have join with CONNECT BY SELECT E. deptno FROM emp CONNECT BY PRIOR empno = mgr START WITH empno = 7839) E .deptno CONNECT BY PRIOr empno = mgr START WITH empno = 7839.00 0.2*level-2)||ename ename.emplevel.1.15) "ENAME".out explain=user/pwd sort=exeela sys=no call count rows ------.--------------Parse 1 0 Execute 1 0 Fetch 2 4 ------.---------. mgr. dept.00 0. LPAD(' '.---------. if the hierarchy table only has surrogate keys.---------. dept. LPAD(' '. empno.5 TKPROF: 4 INDEX (RANGE SCAN) OF 'PK_TARIFF' (UNIQUE) (Cost=2 Card=1) tkprof xyz.deptno.---------.---------. mgr.---------0. (SELECT level emplevel.ename.deptno = dept. SELECT level.00 0. For instance. empno.---------0. dept. However.deptno. E.00 0.dname FROM emp. there are often times you would like to join to them anyway.00 0.00 0 19 4 Misses in library cache during parse: 0 Optimizer goal: CHOOSE Example 3 (cannot have join with CONNECT BY) Have you ever tried to join to a hierarchical query (a query using CONNECT BY and PRIOR) only to get this message: ORA-01437: cannot have join with CONNECT BY One of the limitations of hierarchical queries is that you cannot join to them. This tip shows how you can use "Inline Views" to join tables to a hierarchical query.00 0 0 0 0 0 19 0 0 4 -------.trc xyz. dept.--------------total 4 4 cpu elapsed disk query current -------.dname FROM dept.00 0. dept WHERE emp. SUBSTR(E.2*level-2)||ename ename.

ename from emp WHERE ROWNUM BETWEEN 2 and 3 / no rows selected Using an Inline View to get around this limitation: SELECT t1.rn.---------. For example: SELECT ROWNUM.-------------1 KING 7839 10 ACCOUNTING 2 CLARK 7782 10 ACCOUNTING 3 MILLER 7934 10 ACCOUNTING 2 JONES 7566 20 RESEARCH 3 SCOTT 7788 20 RESEARCH 4 ADAMS 7876 20 RESEARCH 3 FORD 7902 20 RESEARCH 4 SMITH 7369 20 RESEARCH 2 BLAKE 7698 30 SALES 3 ALLEN 7499 30 SALES 3 WARD 7521 30 SALES 3 MARTIN 7654 30 SALES 3 TURNER 7844 30 SALES 3 JAMES 7900 30 SALES Example 3 (ROWNUM 1 Problem) A rownum restriction starting with 1 works: ROWNUM does not work for ranges that don't start at 1.deptno = dept.ename from emp WHERE ROWNUM BETWEEN 1 and 3 / ROWNUM ---------1 2 3 ENAME ---------SMITH ALLEN WARD However.rn BETWEEN 2 and 3 / The main trick to this query is the "internal" select statement.deptno / EMPLEVEL ENAME EMPNO DEPTNO DNAME ---------. A ROWNUM restriction starting with 1 works: SELECT ROWNUM.ename FROM (SELECT ROWNUM rn. t1. if you try to use a range it will not work.WHERE E. ename FROM emp) t1 WHERE t1.--------------.---------. This select statement in the .

What the will get is five random records (the first five we happen to hit). In order to access the internal query's columns from the external query. EMPNO DEPTNO ------------------7839 5000 7788 3000 7902 3000 7566 2975 7698 2850 ENAME JOB MGR HIREDATE SAL COMM ---------.--------.--------.---------.from clause.---------JONES ALLEN 30 WARD 30 MARTIN 30 SMITH MANAGER 20 SALESMAN SALESMAN SALESMAN CLERK 20 7839 02-APR-81 7698 20-FEB-81 7698 22-FEB-81 7698 28-SEP-81 7902 17-DEC-80 1600 1250 1250 The users intention was most likely to get the the top-five paid people . EMPNO DEPTNO ------------------7566 2975 7499 300 7521 500 7654 1400 7369 800 ENAME JOB MGR HIREDATE /* WRONG! */ SAL COMM ---------.a top-N query.---------KING SCOTT FORD JONES BLAKE PRESIDENT 10 ANALYST 20 ANALYST 20 MANAGER 20 MANAGER 30 17-NOV-81 7566 09-DEC-82 7566 03-DEC-81 7839 02-APR-81 7839 01-MAY-81 .---------. basically does a full query of the table.--------. sorted by salary. you get the correct result. The outside query can then operate on the results of the internal 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. then returns the values (along with the psuedo-column ROWNUM) to the "outside" query. you need to alias that column in the internal query in order to refer to it in the outside query: Example 4 (ROWNUM and ORDER BY Problem.---------. If you use an inline view with the ORDER BY inside the inline view. select * from (select * from emp order by sal desc) where rownum <= 5. TOP-N Queries) The following query form is almost wrong: select * from emp where ROWNUM <= 5 order by sal desc.--------.---------.

ROWNUM rn FROM (enter your query here) a WHERE ROWNUM <= :MAX_ROW) WHERE rn >= :MIN_ROW.*. SELECT * FROM (SELECT a.---------SALESMAN 2 SALESMAN 3 MANAGER 20 SALESMAN 5 MANAGER 30 7698 20-FEB-81 7698 22-FEB-81 4 7839 02-APR-81 7698 28-SEP-81 6 7839 01-MAY-81 1250 1600 1250 .--------. The general form of this is as follows: SELECT * FROM (SELECT a. ROWNUM rn FROM (SELECT * FROM emp) a WHERE ROWNUM <= 6) WHERE rn >= 2.---------. EMPNO DEPTNO ------------------7499 300 7521 500 7566 2975 7654 1400 7698 2850 ENAME RN ------------------ALLEN 30 WARD 30 JONES MARTIN 30 BLAKE JOB MGR HIREDATE SAL COMM --------.---------.*.Example 5 (Pagination with ROWNUM) Pagination with ROWNUM can be used to get rows N thru M of a result set.

Sign up to vote on this title
UsefulNot useful