SQL Tuning Tips

In this blog I am going to write about some basic rules and tips that can enhance query performance by reducing parsing,execution, or both. These rules are simple but may yield lots of benefits. TIP1. 1. Use SQL standards within an application. Below are the simple rules that are easy to implement and allow more sharing within Oracle’s memory. a. Using a single case for all SQL verbs b. Beginning all SQL verbs on a new line c. Right or left aligning verbs within the initial SQL verb d. Separating all words with a single space 2. Use bind variables where ever possible. 3. Use a standard approach to table aliases. If two identical SQL statements vary because an identical table has two different aliases, then the SQL is different and will not be shared. 4. Use table aliases and prefix all column names by their aliases when more than one table is involved in a query. This reduces parse time AND prevents future syntax errors if someone adds a column to one of the tables with the same name as a column in another table. (ORA-00918: COLUMN AMBIGUOUSLY DEFINED) TIP2. Don’t perform operation on database objects referenced in the where clause. Oracle will ignore the indexes defined on columns. DO NOT USE SELECT account_name, trans_date, amount FROM transaction WHERE SUBSTR(account_name,1,7) = 'CAPITAL'; SELECT account_name, trans_date, amount FROM transaction WHERE account_name = NVL ( :acc_name, account_name); SELECT account_name, trans_date, amount FROM transaction WHERE TRUNC (trans_date) = TRUNC (SYSDATE); SELECT account_name, trans_date, amount FROM transaction WHERE account_name || account_type = 'AMEXA'; SELECT account_name, trans_date, amount FROM transaction WHERE amount + 3000 < 5000; SELECT account_name, trans_date, amount FROM transaction WHERE amount != 0;

SELECT account_name.99999. amount FROM transaction WHERE trans_date BETWEEN TRUNC (SYSDATE) AND TRUNC (SYSDATE) + . . trans_date. SELECT account_name. AVG (loc_size) location BY region region != 'SYDNEY' region != 'PERTH'. SELECT account_name. TIP3. amount FROM transaction WHERE amount > 0. trans_date. SELECT account_name. amount FROM transaction WHERE amount < 2000. amount FROM transaction WHERE amount > 0. amount FROM transaction WHERE account_name LIKE 'CAPITAL%'. HAVING clause will filter records only after fetching all rows. Using WHERE clause helps reducing overhead in sorting. SELECT account_name. USE SELECT account_name. SELECT account_name. trans_date. DO NOT USE SELECT FROM GROUP HAVING AND region. amount FROM transaction WHERE account_name = 'AMEX' AND account_type = 'A'. trans_date. trans_date. trans_date. amount FROM transaction WHERE amount NOT = 0. HAVING clauses should only be used when columns with summary operations applied to them are restricted by the clause. summing etc. Try not to use HAVING clause in the select statements.SELECT account_name. trans_date. '%'). amount FROM transaction WHERE account_name LIKE NVL ( :acc_name. trans_date.

IN and table joins when doing multiple table joins.USE SELECT FROM WHERE AND GROUP TIP4. select count(subobject_name) from big where object_id in ( select object_id from small ) versus select count(subobject_name) from big where exists ( select null from small where small.g. Combined Subqueries SELECT emp_name FROM emp WHERE (emp_cat. sal_range) = (SELECT MAX (category). . Separate Subqueries SELECT emp_name FROM emp WHERE emp_cat = (SELECT MAX (category) FROM emp_categories) AND emp_range = (SELECT MAX (sal_range)FROM emp_categories) AND emp_dept = 0020. Minimize the number of table lookups (subquery blocks) in queries.object_id ) region.object_id = big. None of these are consistently faster. BY region. If the outer query is "big" and the inner query is "small". AVG (loc_size) location region != 'SYDNEY' region != 'PERTH'. MAX (sal_range) FROM emp_categories) AND emp_dept = 0020. it depends on the volume of data. TIP5. particularly if statements include subquery SELECTs or multicolumn UPDATEs. E. Avoid using subqueries when a JOIN will do the job. "IN" is generally more efficient. Consider the alternatives EXISTS.

The DISTINCT operator causes Oracle to fetch all rows satisfying the table join and then sort and filter out duplicate values. the UNION ALL is much more efficient. E. TIP7. emp e WHERE d. merge or filter. TIP8. 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.dept_no). EXISTS is a faster alternative. because the Oracle optimizer realizes when the subquery has been satisfied once.object_id = big. A UNION ALL simply returns all rows including duplicates and does not have to perform any sort.dept_no = e. there is no need to proceed further and the next matching row can be fetched. or you don't care if duplicates are returned. DO NOT USE SELECT DISTINCT dept_no.If the outer query is "small" and the inner query is "big" "WHERE EXISTS" can be quite efficient.dept_no. 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.object_id ) TIP6.dept_no = d. USE SELECT dept_no. If your tables are mutually exclusive (include no duplicate records). Consider using DECODE to avoid having to scan the same rows repetitively or join the same table repetitively. dept_name FROM dept d. dept_name FROM dept d WHERE EXISTS (SELECT 'X' FROM emp e WHERE e. DO NOT USE .g. select count(subobject_name) from small where object_id in ( select object_id from big ) versus: select count(subobject_name) from small where exists ( select null from big where small. If possible use UNION ALL instead of UNION.

COUNT(*) emp status = 'N' emp_name LIKE 'SMITH%'. salary+0).. SELECT clause scalar subqueries and FROM clause in-line views). TIP13. TIP9.g.. Rewrite complex subqueries with temporary tables .SELECT FROM WHERE AND -SELECT FROM WHERE AND USE COUNT(*) emp status = 'Y' emp_name LIKE 'SMITH%'. making them very fast for reporting SQL. NULL)) Y_count. Use SQL analytic functions . 'N'. If query returns more than 20 percent of the rows in the table. name = 'NAME'). TIP14. salary = 50000).g.. TIP11.g. use the UNION statement instead of OR conditions. 'X'. NULL)) N_count FROM emp WHERE emp_name LIKE 'SMITH%'.Oracle created the global temporary table (GTT) and the SQL WITH operator to help divide-and-conquer complex SQL sub-queries (especially those with with WHERE clause subqueries. SELECT COUNT(DECODE(status. use a full-table scan rather than an index scan. Whenever possible.The Oracle analytic functions can do multiple aggregations (e. With the rule-based optimizer. 'X'. as it will invalidate the index. TIP15. 'Y'. TIP12. COUNT(DECODE(status.g.Using the minus operator instead of NOT IN and NOT EXISTS will result in a faster execution plan. Never mix data types in Oracle queries. remember not to use quotes (e. If the column is numeric. name||') or add zero to a numeric column name (e. To turn off an index (only with a cost-based optimizer). concatenate a null string to the index column name (e. always use single quotes (e. rollup by cube) with a single pass through the tables. ..g. this allows manually choose the most selective index to service the query. TIP10. Use minus instead of EXISTS subqueries . Tuning SQL with temporary tables (and materializations in the WITH clause) can result in amazing performance improvements. For char index columns.

where emp_name IS NULL).create an FBI on ename column with NULL values create index emp_null_ename_idx on emp (nvl(ename.Change the WHERE predicate to match the function Here is an example of using an index on NULL column values: -.Add a hint to force the index 2.If we have SQL that frequently tests for NULL. analyze index emp_null_ename_idx compute statistics. book_key book NOT EXISTS (SELECT book_key FROM sales). TIP17. analyze index emp_null_ename_idx compute statistics.test the index access (change predicate to use FBI) select /*+ index(emp_null_ename_idx) */ename from emp e where nvl(ename.In many cases of NOT queries (but ONLY where a column is defined as NULL).create an FBI on emp_nbr column with NULL values create index emp_null_emp_nbr_idx on emp (nvl(ename.book_key IS NULL. -.book_key = s. Note that we must make one of two changes: 1.book_key FROM book b. . Above two can be re-written as SELECT b. E. we can create a function-based index using the null value built-in SQL function to index only on the NULL columns. Note that this is a non-correlated sub-query. Same techniques with NULL numeric values. Index NULL values . but it could be re-written as an outer join. SELECT FROM WHERE SELECT FROM WHERE book_key book book_key NOT IN (SELECT book_key FROM sales).g. sales s WHERE b.'null')). consider creating an index on NULL values.o)).TIP16. Re-write NOT IN and NOT EXISTS subqueries as outer joins .e.book_key(+) AND s.'null') = 'null'. you can re-write the uncorrelated subqueries into outer joins with IS NULL tests. values with a zero: This syntax replaces NULL -. To get around the optimization of SQL queries that choose NULL column values (i. Now we can use the index and greatly improve the speed of any queries that require access to the NULL columns.

exec dbms_stats.TIP18. estimate_percent => 15). estimate_percent => 15). Execution plan includes. estimate_percent => dbms_stats. exec dbms_stats. method_opt => 'for all columns size repeat'. Syntax: exec dbms_stats.auto_sample_size.gather_table_stats('SCOTT'. oracle relies on information about the tables and indexes in the query. and which internal join methods to use (Oracle has nested loop joins. star joins. cascade => true. hash joins. 'EMPLOYEES_PK').gather_table_stats('SCOTT'.gather_database_stats. 'EMPLOYEES'). exec dbms_stats.gather_schema_stats('SCOTT'.gather_index_stats('SCOTT'. what order in which to join multiple tables together.gather_schema_stats( ownname => 'SCOTT'. Achieve faster SQL performance with dbms_stats. and sort merge join methods). options => 'GATHER AUTO'. exec dbms_stats. estimate_percent => 15). which index to use to retrieve table row. exec dbms_stats. degree => 15 ). 'EMPLOYEES_PK'.gather_database_stats(estimate_percent => 15).gather_schema_stats('SCOTT'). exec dbms_stats. exec dbms_stats. . 'EMPLOYEES'.gather_index_stats('SCOTT'. These execution plans are computed by the Oracle costbased SQL optimizer commonly known as the CBO. To choose the best execution plan for a SQL query. Use dbms_stats utility to estimate the stats. exec dbms_stats.