Professional Documents
Culture Documents
Try to avoid operations on database objects referenced in the WHERE clause. 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;
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;
Combined Subqueries
Minimize the number of table lookups (subquery blocks) in queries, particularly if your statements include subquery SELECTs or multicolumn UPDATEs. 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,comm) = (SELECT MAX (sal), MAX(comm) FROM lookup);
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 optimizer realizes when the subquery has been satisfied once, there is no need to proceed further and the next matching row can be fetched. Given Query SELECT DISTINCT d.deptno, d.dname FROM dept D, emp E WHERE D.deptno = E.deptno; Alternative SELECT d.deptno, d.dname FROM dept D WHERE EXISTS (SELECT 'X' FROM emp E WHERE E.deptno = D.deptno);
UNION ALL
Consider whether a UNION ALL will suffice in place of a UNION. 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. A UNION ALL simply returns all rows including duplicates and does not have to perform any sort, merge or filter. If your tables are mutually exclusive (include no duplicate records), or you don't care if duplicates are returned, the UNION ALL is much more efficient.
UNION SELECT acct, balance FROM debit WHERE trandate = '31-DEC-95' UNION SELECT acct, balance FROM credit WHERE trandate = '31-DEC-95';
UNION ALL SELECT acct, balance FROM debit WHERE trandate = '31-DEC-95' UNION ALL SELECT acct, balance FROM credit WHERE trandate = '31-DEC-95';
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 be 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 FROM WHERE AND dept.* dept, emp dept.deptno = emp.deptno (+) emp.ROWID IS NULL;
SELECT * FROM dept WHERE NOT EXISTS (SELECT NULL FROM emp WHERE emp.deptno = dept.deptno);
7521 7566 7654 7698 7782 7788 7839 7844 7876 7900 7902 7934
WARD JONES MARTIN BLAKE CLARK SCOTT KING TURNER ADAMS JAMES FORD MILLER
30 20 30 30 9 20 9 30 20 30 20 9 10 40
SALES RESEARCH SALES SALES RESEARCH SALES RESEARCH SALES RESEARCH ACCOUNTING OPERATIONS
7521 7566 7654 7698 7782 7788 7839 7844 7876 7900 7902 7934
WARD JONES MARTIN BLAKE CLARK SCOTT KING TURNER ADAMS JAMES FORD MILLER
30 20 30 30 9 20 9 30 20 30 20 9 10 40
SALES RESEARCH SALES SALES RESEARCH SALES RESEARCH SALES RESEARCH ACCOUNTING OPERATIONS
Inline VIEWS
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.
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. If you have a query as the following ... SELECT FROM WHERE AND a table id = :id 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.
FOR TABLE FOR ALL INDEXES FOR ALL INDEXED COLUMNS; Table analyzed. alter session set timed_statistics=true; alter session set sql_trace=true; VARIABLE b1 NUMBER exec :b1 := 10 ALTER SESSION SET TIMED_STATISTICS=TRUE; ALTER SESSION SET SQL_TRACE=TRUE; SELECT from WHERE AND max(height) test id = :b1 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) tkprof gek1_ora_16520.trc gek1_ora_16520.out explain=scott/tiger sort=exeela sys=no
cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 2 0 0.00 0.00 0 2 0 -------- ---------- ---------- ---------- ---------0.00 0.00 0 4 0
0.00 0.00 0 0 0 0.00 0.00 0 2 0 -------- ---------- ---------- ---------- ---------0.03 0.06 2 43 0
0 1 ---------1
With Subquery
alter session set timed_statistics=true; select value from v$parameter where name = 'user_dump_dest'; alter session set sql_trace=true; VARIABLE b1 VARCHAR2(19) exec :b1 := '07.04.1999:13:30:31' VARIABLE b2 NUMBER exec :b2 := 2 VARIABLE b3 NUMBER exec :b3 := 317
SELECT switch_time, rat_id FROM tariff WHERE effdate = (SELECT MAX(effdate) FROM tariff WHERE effdate <= TRUNC(TO_DATE(:b1,'DD.MM.YYYY:HH24:MI:SS')) AND weekday = :b2 AND T_ID = :b3) AND TO_CHAR(switch_time,'HH24:MI') <= TO_CHAR(TO_DATE(:b1,'DD.MM.YYYY:HH24:MI:SS'),'HH24:MI') AND weekday = :b2 AND t_id = :b3 ORDER BY TO_CHAR(switch_time,'HH24:MI') DESC; 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: tkprof xyz.trc xyz.out explain=user/pwd sort=exeela sys=no call count ------- -----Parse 1 Execute 1 Fetch 2 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.01 0.00 0 38 8 rows ---------0 0 4
------- -----total 4
---------4
4 5 TKPROF:
3 4
TABLE ACCESS (BY INDEX ROWID) OF 'TARIFF' (Cost=2 Card=1 Bytes=21) INDEX (RANGE SCAN) OF 'PK_TARIFF' (UNIQUE) (Cost=2 Card=1)
tkprof xyz.trc xyz.out explain=user/pwd sort=exeela sys=no call count ------- -----Parse 1 Execute 1 Fetch 2 ------- -----total 4 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 19 4 -------- ---------- ---------- ---------- ---------0.00 0.00 0 19 4 rows ---------0 0 4 ---------4
ORA-01437: cannot have join with CONNECT BY SELECT E.emplevel, SUBSTR(E.ename,1,15) "ENAME", E.empno, dept.deptno, dept.dname FROM dept, (SELECT level emplevel, LPAD(' ',2*level-2)||ename ename, empno, mgr, deptno FROM emp CONNECT BY PRIOR empno = mgr START WITH empno = 7839) E WHERE E.deptno = dept.deptno / EMPLEVEL ENAME EMPNO DEPTNO DNAME ---------- --------------- ---------- ---------- -------------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
ROWNUM ---------1 2 3
However, if you try to use a range it will not work. For example: SELECT ROWNUM,ename from emp WHERE ROWNUM BETWEEN 2 and 3 / no rows selected Using an Inline View to get around this limitation: SELECT t1.rn, t1.ename FROM (SELECT ROWNUM rn, ename FROM emp) t1 WHERE t1.rn BETWEEN 2 and 3 / 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:
JOB MGR HIREDATE SAL COMM DEPTNO --------- ---------- --------- ---------- ---------- ---------MANAGER 7839 02-APR-81 2975 20 SALESMAN 7698 20-FEB-81 1600 300 30 SALESMAN 7698 22-FEB-81 1250 500 30 SALESMAN 7698 28-SEP-81 1250 1400 30 CLERK 7902 17-DEC-80 800 20
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; EMPNO ---------7839 7788 7902 7566 7698 ENAME ---------KING SCOTT FORD JONES BLAKE JOB MGR HIREDATE SAL COMM DEPTNO --------- ---------- --------- ---------- ---------- ---------PRESIDENT 17-NOV-81 5000 10 ANALYST 7566 09-DEC-82 3000 20 ANALYST 7566 03-DEC-81 3000 20 MANAGER 7839 02-APR-81 2975 20 MANAGER 7839 01-MAY-81 2850 30
WHERE ROWNUM <= 6) WHERE rn >= 2; EMPNO ---------7499 7521 7566 7654 7698 ENAME ---------ALLEN WARD JONES MARTIN BLAKE JOB MGR HIREDATE SAL COMM DEPTNO RN --------- ---------- --------- ---------- ---------- ---------- ---------SALESMAN 7698 20-FEB-81 1600 300 30 2 SALESMAN 7698 22-FEB-81 1250 500 30 3 MANAGER 7839 02-APR-81 2975 20 4 SALESMAN 7698 28-SEP-81 1250 1400 30 5 MANAGER 7839 01-MAY-81 2850 30 6