You are on page 1of 51

Presentation Outline

SQL Writing Process SQL Standards Using Indexes The Optimizer FROM, WHERE Clauses EXPLAIN SQL Trace Sub-Selects and Joins Tips and Tricks
1

Caveat
Although many of these principles apply to all databases, Oracle will be used in the examples.

SQL Writing Process


Step 1: What information do I need? Columns Step 2: Where is it? Tables Step 3: Write SQL:
SELECT columns FROM tables WHERE ... (joins, filters, subqueries)

I'M FINISHED!

SQL Writing Process


YOU'RE NOT FINISHED YET! You've got the results you want, but at what cost? There are many, many ways to get the right results, but only one is the fastest way1000-to-1 improvements are attainable! Inefficient SQL can dramatically degrade the performance of the entire system Developers and DBAs must work together to tune the database and the application
4

Pre-Tuning Questions
How long is too long? Is the statement running on near-production volumes? Is the optimal retrieval path being used? How often will it execute? When will it execute?

SQL Standards
Why are SQL standards important? Maintainability, readability Performance: If SQL is the same as a (recently) executed statement, it can be re-used instead of needing to be reparsed

SQL Standards
Question: which of these statements are the same? A. SELECT LNAME FROM EMP WHERE EMPNO = 12; B. SELECT lname FROM emp WHERE empno = 12; C. SELECT lname FROM emp WHERE empno = :id; D. SELECT lname FROM
WHERE empno = 12; emp

SQL Standards
Answer: None Whitespace, case, bind variables vs. constants all matter Using standards helps to ensure that equivalent SQL can be reused.

Tables Used in the Examples


DEPT deptno dname loc EMP empno mgr job deptno fname lname comm hiredate grade sal
9

SALGRADE grade losal hisal

SQL Standards: Example


SELECT E.empno, D.dname FROM emp E, dept D WHERE AND OR E.deptno = D.deptno (D.deptno = :vardept E.empno = :varemp); Keywords upper case and left-aligned Columns on new lines Use std. table aliases Separate w/ one space Use bind variables AND/OR on new lines No space before/after parentheses

10

Indexes: What are they?


An index is a database object used to speed retrieval of rows in a table. The index contains only the indexed value--usually the key(s)--and a pointer to the row in the table. Multiple indexes may be created for a table Not all indexes contain unique values Indexes may have multiple columns (e.g., Oracle allows up to 32)
11

Indexes and SQL


If a column appears in a WHERE clause it is a candidate for being indexed. If a column is indexed the database can used the index to find the rows instead of scanning the table. If the column is not referenced properly, however, the database may not be able to used the index and will have to scan the table anyway. Knowing what columns are and are not indexed can help you write more efficient SQL
12

Example: Query without Index


No index exists for column EMPNO on table EMP, so a table scan must be performed:
Table: EMP SELECT * FROM emp WHERE empno = 8
empno 4 9 1 3 5 2 7 8 6 fname lisa jackie john larry jim mary harold mark gene lname... baker miller larson jones clark smith simmons burns harris

13

Example: Query with Index


Column EMPNO is indexed, so it can be used to find the requested row:
SELECT * FROM emp WHERE empno = 8 Index: PK_EMP EMP (EMPNO)
1, 4 1 2

Table: EMP
5 5, 9 empno 4 9 1 3 5 2 7 8 6 fname lisa jackie john larry jim mary harold mark gene lname ... baker miller larson jones clark smith simmons burns harris

7 8 9

14

Indexes: Caveats
Sometimes a table scan cannot be avoided Not every column should be indexed--there is performance overhead on Inserts, Updates, Deletes Small tables may be faster with a table scan Queries returning a large number (> 5-20%) of the rows in the table may be faster with a table scan

15

Indexes: Column Order


Example: Index on (EMPNO, DEPTNO)
SELECT * FROM emp WHERE deptno = 10; SELECT FROM WHERE AND * emp empno > 0 deptno = 10;

Will NOT use index

WILL use index

Must use the leading column(s) of the index for the index to be used
16

Indexes: Functions
Using a function, calculation, or other operation on an indexed column disables the use of the Index
SELECT FROM WHERE ... WHERE * Will NOT use index emp TRUNC(hiredate) = TRUNC(SYSDATE); fname || lname = 'MARYSMITH';

SELECT * FROM emp WHERE hiredate BETWEEN TRUNC(SYSDATE) AND TRUNC(SYSDATE)+1 ... WHERE fname = 'MARY' WILL use index AND lname = 'SMITH';
17

Indexes: NOT
Using NOT excludes indexed columns:
SELECT FROM WHERE ... ... * dept deptno != 0; deptno NOT = 0; deptno IS NOT NULL;

Will NOT use index

SELECT * FROM dept WHERE deptno > 0;

WILL use index

18

The Optimizer
The WHERE/FROM rules on the following pages apply to the Rule-based optimizer (Oracle). If the Cost-based Optimizer is used, Oracle will attempt to reorder the statements as efficiently as possible (assuming statistics are available). DB2 and Sybase use only a Cost-based optimizer The Optimizer's access paths can be overridden in Oracle and Sybase (not DB2)

19

The Optimizer: Hints


Return the first rows in the result set as fast as possible:
SELECT /*+ FIRST_ROWS */ empno FROM emp E dept D, WHERE E.deptno = D.deptno;

Force Optimizer to use index IDX_HIREDATE:


SELECT /*+ INDEX (E idx_hiredate) */ empno FROM emp E WHERE E.hiredate > TO_DATE('01-JAN-2000');

20

FROM Clause: Driving Table


Specify the driving table last in the FROM Clause:
SELECT * FROM dept D, -- 10 rows emp E -- 1,000 rows WHERE E.deptno = D.deptno; SELECT * FROM emp E, -- 1,000 rows dept D -- 10 rows WHERE E.deptno = D.deptno;

Driving table is EMP

Driving table is DEPT

21

FROM Clause: Intersection Table


When joining 3 or more tables, use the Intersection table (with the most shared columns) as the driving table:
SELECT * FROM dept D, salgrade S, emp E WHERE E.deptno = D.deptno AND E.grade = S.grade;

EMP shares columns with DEPT and SALGRADE, so use as the driving table

22

WHERE: Discard Early


Use WHERE clauses first which discard the maximum number of rows:
SELECT FROM WHERE AND * emp E E.empno IN (101, 102, 103) E.deptno > 10;

3 rows 90,000 rows

23

WHERE: AND Subquery First


When using an "AND" subquery, place it first:
SELECT FROM WHERE AND * CPU = 156 sec emp E E.sal > 50000 25 > (SELECT COUNT(*) FROM emp M WHERE M.mgr = E.empno)

SELECT * CPU = 10 sec FROM emp E WHERE 25 > (SELECT COUNT(*) FROM emp M WHERE M.mgr = E.empno) AND E.sal > 50000
24

WHERE: OR Subquery Last


When using an "OR" subquery, place it last:
SELECT * CPU = 100 sec FROM emp E WHERE 25 > (SELECT COUNT(*) FROM emp M WHERE M.mgr = E.empno) OR E.sal > 50000 SELECT FROM WHERE OR * CPU = 30 sec emp E E.sal > 50000 25 > (SELECT COUNT(*) FROM emp M WHERE M.mgr = E.empno)
25

WHERE: Filter First, Join Last


When Joining and Filtering, specify the Filter condition first, Joins last.
SELECT * FROM emp E, dept D WHERE (E.empno = 123 OR D.deptno > 10) AND E.deptno = D.deptno;

Filter criteria Join criteria

26

Subqueries: IN vs. EXISTS


Use EXISTS instead of IN in subqueries:
SELECT E.* IN: Both tables are FROM emp E scanned WHERE E.deptno IN ( SELECT D.deptno FROM dept D WHERE D.dname = 'SALES'); SELECT * FROM emp E WHERE EXISTS SELECT FROM WHERE AND EXISTS: Only outer table is scanned; subquery uses index

( 'X' dept D D.deptno = E.deptno D.dname = 'SALES');


27

Subquery vs. Join


Use Join instead of Subquery :
SELECT * IN: Both tables are FROM emp E scanned WHERE E.deptno IN ( SELECT D.deptno FROM dept D WHERE D.dname = 'SALES'); SELECT E.* FROM emp E, dept D WHERE D.dname = 'SALES' AND D.deptno = E.deptno;
28

JOIN: Only one table is scanned, other uses index

Join vs. EXISTS


Best performance depends on subquery/driving table:
SELECT * FROM emp E WHERE EXISTS SELECT FROM WHERE AND EXISTS: better than Join if the number of matching rows in DEPT is small ( 'X' dept D D.deptno = E.deptno D.dname = 'SALES');

SELECT E.* FROM emp E, dept D WHERE D.dname = 'SALES' AND D.deptno = E.deptno;
29

JOIN: better than Exists if the number of matching rows in DEPT is large

Explain
Display the access path the database will use (e.g., use of indexes, sorts, joins, table scans) Oracle: Sybase: DB2: EXPLAIN SHOWPLAN EXPLAIN

Oracle Syntax:
EXPLAIN PLAN SET STATEMENT_ID = 'statement id' INTO PLAN_TABLE FOR statement

Requires Select/Insert privileges on PLAN_TABLE


30

Explain
Example 1: IN subquery
SELECT * FROM emp E WHERE E.deptno IN ( SELECT D.deptno FROM dept D WHERE D.dname = 'SALES');

Result:

3 joins MERGE JOIN 1 dynamic view SORT (JOIN) 2 table scans TABLE ACCESS (FULL) OF EMP 3 sorts SORT (JOIN) VIEW SORT (UNIQUE) TABLE ACCESS (FULL) OF DEPT
31

Explain
Example 2: "EXISTS" subquery
SELECT * FROM emp e WHERE EXISTS SELECT FROM WHERE AND ( 'x' dept d d.deptno = e.deptno d.dname = 'SALES'); 1 table scan 1 index scan 1 index access

Result:

FILTER TABLE ACCESS (FULL) OF EMP TABLE ACCESS (BY INDEX ROWID) OF DEPT INDEX (UNIQUE SCAN) OF PK_DEPT (UNIQUE)
32

Explain
Example 3: Join (no subquery)
SELECT E.* FROM emp E, dept D WHERE D.dname = 'SALES' AND D.deptno = E.deptno;

Result:

1 table scan 1 index scan 1 index access

NESTED LOOPS TABLE ACCESS (FULL) OF EMP TABLE ACCESS (BY INDEX ROWID) OF DEPT INDEX (UNIQUE SCAN) OF PK_DEPT (UNIQUE)

33

SQL Trace
Use SQL Trace to determine the actual time and resource costs for for a statement to execute. Step 1: ALTER SESSION SET SQL_TRACE TRUE; Step 2: Execute SQL to be traced:
SELECT E.* FROM emp E, dept D WHERE D.dname = 'SALES' AND D.deptno = E.deptno;

Step 3: ALTER SESSION SET SQL_TRACE FALSE;


34

SQL Trace
Step 4: Trace file is created in <USER_DUMP_DEST> directory on the server (specified by the DBA). Step 5: Run TKPROF (UNIX) to create a formatted output file:
tkprof echd_ora_15319.trc $HOME/prof.out table=plan_table explain=dbuser/passwd Trace file Formatted output file destination for Explain user/passwd for Explain

35

SQL Trace
Step 6: view the output file:
... SELECT E.* FROM emp E, dept D WHERE D.dname = 'SALES' AND D.deptno = E.deptno; call count ------- -----Parse 1 Execute 1 Fetch 2 ------- -----total 4

TIMED_STATISTICS must be turned on to get these values


rows ---------0 0 6 ---------6

cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 4 19 3 -------- ---------- ---------- ---------- ---------0.00 0.00 4 19 3

Misses in library cache during parse: 0 Optimizer goal: CHOOSE Parsing user id: 62 (PMARKS) Rows ------6 14 14 14 Row Source Operation --------------------------------------------------NESTED LOOPS TABLE ACCESS FULL EMP TABLE ACCESS BY INDEX ROWID DEPT INDEX UNIQUE SCAN (object id 4628)

EXPLAIN output

36

Tips and Tricks: UNION ALL


Use UNION ALL instead of UNION if there are no duplicate rows (or if you don't mind duplicates):
SELECT * FROM emp UNION SELECT * FROM emp_arch; SELECT * FROM emp UNION ALL SELECT * FROM emp_arch; UNION: requires sort

UNION ALL: no sort

37

Tips and Tricks: HAVING vs. WHERE


With GROUP BY, use WHERE instead of HAVING (if the filter criteria does not apply to a group function):
SELECT deptno, AVG(sal) FROM emp GROUP BY deptno HAVING deptno IN (10, 20); SELECT deptno, AVG(sal) FROM emp WHERE deptno IN (10, 20) GROUP BY deptno;
38

HAVING: rows are filtered after result set is returned

WHERE: rows are filtered first--possibly far fewer to process

Tips and Tricks: EXISTS vs DISTINCT


Use EXISTS instead of DISTINCT to avoid implicit sort (if the column is indexed):
SELECT DISTINCT e.deptno, e.lname FROM dept d, emp e WHERE d.deptno = e.deptno; DISTINCT: implicit sort is performed to filter duplicate rows

SELECT e.deptno, e.lname EXISTS: no sort FROM emp e WHERE EXISTS ( SELECT 'X' FROM dept d WHERE d.deptno = e.deptno);
39

Tips and Tricks: Consolidate SQL


Select from Sequences and use SYSDATE in the statement in which they are used:
SELECT SYSDATE INTO :vardate FROM dual; FROM dual;

SELECT arch_seq.NEXTVAL INTO :varid

BEFORE: 3 statements INSERT INTO archive VALUES (:vardate, :varid, ...) are used to perform 1 Insert INSERT INTO emp_archive VALUES (SYSDATE, emp_seq.NEXTVAL, ...) AFTER: only 1 statement is needed
40

Tips and Tricks: Consolidate SQL


Consolidate unrelated statements using outer-joins to the the DUAL (dummy) table:
SELECT dname FROM dept WHERE deptno = 10; SELECT lname FROM emp WHERE empno = 7369; BEFORE: 2 round-trips SELECT d.dname, e.lname FROM dept d, emp e, AFTER: only 1 round-trip dual x WHERE d.deptno (+) = 10 AND e.empno (+) = 7369 AND NVL('X', x.dummy) = NVL('X', e.ROWID (+)) AND NVL('X', x.dummy) = NVL('X', d.ROWID (+));
41

Tips and Tricks: COUNT


Use COUNT(*) instead of COUNT(column):
SELECT COUNT(empno) FROM emp; SELECT COUNT(*) FROM emp;

~ 50% faster

42

Tips and Tricks: Self-Join


Use a self-join (joining a table to itself) instead of two queries on the same table:
SELECT mgr INTO :varmgr FROM emp WHERE deptno = 10; LOOP... SELECT mgr, lname FROM emp WHERE mgr = BEFORE: 2 round-trips :varmgr; SELECT E.mgr, E.lname FROM emp E, emp M WHERE M.deptno = 10 AND E.empno = M.mgr;
43

AFTER: only 1

Tips and Tricks: ROWNUM


Use the ROWNUM pseudo-column to return only the first N rows of a result set. (For example, if you just want a sampling of data):
SELECT * FROM emp WHERE ROWNUM <= 10; Returns only the first 10 employees in the table, in no particular order

44

Tips and Tricks: ROWID


The ROWID pseudo-column uniquely identifies a row, and is the fastest way to access a row:
CURSOR retired_emp_cur IS Instead of selecting the SELECT ROWID key column(s), ROWID is FROM emp used to identify the row WHERE retired = 'Y'; for later use ... FOR retired_emp_rec IN retired_emp_cur LOOP SELECT fname || ' ' || lname INTO :printable_name FROM emp WHERE ROWID = retired_emp_rec.ROWID; ...
45

Tips and Tricks: Sequences


Use a Sequence to generate unique values for a table:
SELECT INTO FROM ... INSERT VALUES MAX(empno) :new_empno emp; INTO emp (:new_empno + 1, ...); MAX(empno) requires a sort and an index scan INSERT could fail with a Duplicate error if someone else gets there first

Using a Sequence INSERT INTO emp VALUES (emp_seq.NEXTVAL, ...); ensures that you always have a unique number, or and does not require any SELECT emp_seq.NEXVAL table reads INTO :new_empno FROM dual;
46

Tips and Tricks: Connect By


Use CONNECT BY to construct hierarchical queries:
SELECT LPAD(' ',4*(LEVEL-1)) || lname Name, Job FROM emp WHERE job != 'CLERK' START WITH job = 'PRESIDENT' Name Job CONNECT BY PRIOR empno = mgr;
King Jones Scott Ford Blake Allen Ward Martin Turner Clark

PRESIDENT MANAGER ANALYST ANALYST MANAGER SALESMAN SALESMAN SALESMAN SALESMAN MANAGER

47

Tips and Tricks: Cartesian Products


Avoid Cartesian products by ensuring that the tables are joined on all shared keys:
SELECT FROM * dept, -- 10 rows salgrade, -- 20 rows emp; -- 1,000 rows

10 * 1000 * 20 = 200,000 rows


SELECT FROM WHERE AND * dept, -- 10 rows salgrade, -- 20 rows emp -- 1,000 rows E.deptno = D.deptno E.grade = S.grade;
48

1,000 rows

Tips and Tricks: TOAD


Tool for Oracle Application Developers Oracle only! Requires Oracle SQL*Net client software Freeware tool for viewing/updating Oracle objects http://www.toadsoft.com or s:\tempfile\toad\toadfree.zip

49

Tips and Tricks: TOAD


CTRL+E displays EXPLAIN PLAN SQL result set displayed in grid

50

Tips and Tricks: TOAD

Indexes, constraints, grants, etc. for the current table

All tables/views for a selected schema


51

Table/view data in an editable grid

You might also like