Search This Blog

Wednesday 6 February 2013

Oracle Query to Find Leap Year

select 1994, decode( mod(1994, 4), 0,
                     decode( mod(1994, 400), 0, 'Leap Year',
                     decode( mod(1994, 100), 0, 'Not a Leap Year', 'Leap Year') ),
                     'Not a Leap Year' )
                     as leap_year_indicator
from dual

How To Find Index on Oracle Table?

select
substr(index_name,1,30) Index_nm,
substr(table_name,1,9) Table_nm,
substr(column_name,1,15) Column_nm,column_position
from all_ind_columns where table_name =upper('&table')
order by index_name,column_position

Oracle Dynamic SQL

What is Dynamic SQL?

Dynamic SQL is SQL that does not have to be hard-coded into your programs. Dynamic SQL refers to the Data Manipulation Language (DML) and Data Definition Language (DDL) statements that you store in character strings and concatenate to suit your needs. You can build Dynamic SQL statements on the fly, allowing the entire statement to be created at runtime. Since all or part of your SQL statement does not need to be known until runtime, you have much greater flexibility in adapting your SQL statements to particular runtime requirements. You can create more general-purpose procedures, and you can simplify your code by not having to write a cursor for every SQL statement. Furthermore, since you can parse any DDL or DML statement using Dynamic SQL, this helps you to compensate for the inability to parse DDL statements directly from within PL/SQL blocks.
Dynamic SQL is made possible by the DBMS_SQL package, which was released with PL/SQL 2.1 (RDBMS 7.1). DBMS_SQL provides the ability to parse the SQL statements that you create from character strings, execute the resulting SQL, and store the results in columns or variables. The DBMS_SQL package also includes related functions and procedures to aid you in this effort, such as a function to track the number of rows processed, a function to track the last ROWID returned, and procedures to allow you to define columns containing the results of your queries.

Why Use Dynamic SQL?

The usefulness of Dynamic SQL will become clearer as several examples are presented. A short list of the benefits derived from using Dynamic SQL may be helpful to a developer new to this topic.
Dynamic SQL is easy to learn and use.
Within several hours, the author was using Dynamic SQL on a current Forms development project. As you will see below, there are very few main procedures and functions in the DBMS_SQL package, and after a few short examples, you will gain the proficiency to use them.
Dynamic SQL allows the SQL to be unknown at compile time.
Dynamic SQL can allow to you handle a situation where any or all of the following are unknown at compile time:
References to database objects, objects such as tables, views, sequences, etc.
Text of the SQL statement (including clauses, join conditions, columns, etc.)
The number of variables that you will need to include in the SQL statement.
The datatypes of variables and/or columns.
Dynamic SQL provides better performance than cursors in some cases.
Dynamic SQL will allow you to use specific indexes in your SQL statements to optimize queries, whereas regular cursors with variables may not allow you to do so. With Dynamic SQL, you can specify which indexes to use through the use of hints, which can be concatenated to your SQL statements dynamically. When working with Forms 4.5, the author was unable to utilize cursors with variables, because the variables used with such cursors would sometimes negate the indexes used in the optimization of the queries. This problem also exists in Forms 5.0. Thus, these cursors performed full table scans, even when hints were used, dramatically decreasing performance. With Dynamic SQL, the author was able to correct this problem and greatly improve performance.
When weighing these benefits against the cost of learning to use Dynamic SQL, the author has found that the benefits far outweigh the cost.
Simple Examples of Dynamic SQL
Below is a simple procedure which you can execute in SQL*PLUS to create a table, using SQL stored in a character string. This example notes the SQL*PLUS resulting execution statements. You can use this procedure to create a table with any name and column names that you would like.
Example of Dynamic SQL (DDL)
CREATE OR REPLACE PROCEDURE DDLDYNAMIC
(TABLENAME VARCHAR2, COLUMN_NAMES VARCHAR2)
AS
my_cursor INTEGER;
BEGIN
my_cursor:= DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(my_cursor, 'CREATE TABLE ' ||TABLENAME ||
' ( ' || COLUMN_NAMES || ' ) ', DBMS_SQL.V7);
DBMS_SQL.CLOSE_CURSOR(my_cursor);
END;
/
To execute this procedure and create a table, you could issue the following command. The SQL*PLUS feedback is listed.
SQL> exec ddldynamic('CUSTOMER','CUSTID NUMBER, NAME VARCHAR2(45)');
PL/SQL procedure successfully completed.
SQL>
Note: you may run into a problem, where the CREATE TABLE privilege is granted to you via a role. In this case, if you use the DBMS_SQL package to create a table, you will receive the Oracle insufficient privileges error, ‘ORA-01031.’ To get around this problem, you may grant the relevant privilege to the user directly. They will then be able to create a table using this procedure.
SQL> CONNECT SYSTEM/MANAGER;
Connected.
SQL> GRANT CREATE TABLE TO SCOTT;
Grant succeeded.

Example of Dynamic SQL (DML)
This example illustrates how to run a simple SELECT statement with one variable passed in, using Dynamic SQL. This procedure accepts a numeric argument, used as the customer ID in a "greater than" clause, concatenates the customer ID with the SELECT statement, runs the statement, and returns the CUSTID and NAME rows.

CREATE OR REPLACE PROCEDURE DMLWITHVAR2 (CUSTID_LOW_VALUE NUMBER) AS
my_cursor INTEGER;
NUM_ROWS_PROCESSED INTEGER;
CUSTID NUMBER;
NAME VARCHAR2(45);
SQLSTR VARCHAR2(70);
BEGIN
SQLSTR := 'SELECT CUSTID, NAME FROM CUSTOMER WHERE CUSTID > '||CUSTID_LOW_VALUE;
my_cursor:= DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE (my_cursor, SQLSTR, DBMS_SQL.V7);
DBMS_SQL.DEFINE_COLUMN (my_cursor, 1, CUSTID);
DBMS_SQL.DEFINE_COLUMN (my_cursor, 2, NAME, 45);
NUM_ROWS_PROCESSED := DBMS_SQL.EXECUTE (my_cursor);
LOOP
IF DBMS_SQL.FETCH_ROWS (my_cursor) > 0 THEN
DBMS_SQL.COLUMN_VALUE (my_cursor, 1, CUSTID);
DBMS_SQL.COLUMN_VALUE (my_cursor, 2, NAME);
DBMS_OUTPUT.PUT_LINE(TO_CHAR(CUSTID) || ' ' || NAME);
ELSE
EXIT;
END IF;
END LOOP;
DBMS_SQL.CLOSE_CURSOR (my_cursor);
END;
/
In order to run this procedure within SQL*PLUS, you must first turn serveroutput on, in order to see the results.
SQL> set serveroutput on
Executing this statement with the CUSTID_LOW_VALUE argument of 100 returns the following query output. Note that the CUSTID column on the left begins with 101, which is greater than the value of 100 that you supplied to the procedure at runtime. This follows from your query condition of CUSTID > CUSTID_LOW_VALUE.
SQL> EXEC DMLWITHVAR2(100);
101 TKB SPORT SHOP
102 VOLLYRITE
103 JUST TENNIS
104 EVERY MOUNTAIN
105 K + T SPORTS
106 SHAPE UP
107 WOMENS SPORTS
108 NORTH WOODS HEALTH AND FITNESS SUPPLY CENTER
PL/SQL procedure successfully completed.

Major DBMS_SQL Functions/Procedures
This section will briefly list the main functions and procedures within the DBMS_SQL package, to explain their usage with Dynamic SQL.

A. DBMS_SQL.PARSE
The PARSE command checks the syntax of your SQL statement and associates the statement with the cursor in your program, which is defined with an INTEGER variable. The PARSE command can parse any DDL or DML statement. Note that with DDL statements, the PARSE executes an implied commit. PARSE takes 3 parameters:
The name of the cursor handle (name of the INTEGER variable that you have declared)
The SQL statement to be parsed (which may be either a literal string of your SQL in single quotes, or the name of a character variable holding your SQL statement)
The language flag
The language flag, either V6, V7, or NATIVE, determines how the SQL statement will be processed, by either version 6, 7, or whichever version is native to your system. The PARSE statement below will use the cursor handle MY_CURSOR, parse the SQL statement in the variable my_sql and use version 7.
DBMS_SQL.PARSE(my_cursor, my_sql, dbms_sql.v7);

B. DBMS_SQL.DEFINE_COLUMN
The DEFINE_COLUMN statement defines the columns to contain the output of your Dynamic SELECT statement. When using a query, you must use DEFINE_COLUMN statements to specify the variables that will hold the selected values. The positioning of the DEFINE_COLUMN statements corresponds to the order in which the columns appear in the SELECT list. For example, the following code specifies two output variables to hold the columns returned by the cursor MY_CURSOR in the following query:
SELECT CUSTOMER_ID, CUSTOMER_NAME FROM CUSTOMER;
DBMS_SQL.DEFINE_COLUMN(MY_CURSOR, 1, CUSTID);
DBMS_SQL.DEFINE_COLUMN(MY_CURSOR, 2, NAME, 45);
Note that when the receiving variable is an INTEGER, as in the case of CUSTID, no length needs to be coded. However, with VARCHAR2 variables, such as NAME, the length must be specified.

C. DBMS_SQL.EXECUTE
The Execute statement executes your SQL statement, and it takes the name of your cursor as an argument.
ignore := DBMS_SQL.EXECUTE(MY_CURSOR);

D. DBMS_SQL.FETCH_ROWS
The FETCH ROWS statement fetches the rows of data that are returned by your query. Each call of the FETCH_ROWS statement returns another row of data. This statement takes the name of your cursor as an argument.
DBMS_SQL.FETCH_ROWS(MY_CURSOR);

E. DBMS_SQL.COLUMN_VALUE
You use this statement to retrieve the values returned from your query, so that you can use them in your program. The first argument is the name of the cursor that you are using. The second argument is the numerical order of the column that you wish to retrieve, based on that column’s order in your select statement, beginning with 1. The third argument is the variable that you have defined to hold the returned value.
DBMS_SQL.COLUMN_VALUE(MY_CURSOR, 1, CUSTID);
DBMS_SQL.COLUMN_VALUE(MY_CURSOR, 2, NAME);

F. DBMS_SQL.OPEN_CURSOR AND DBMS_SQL.CLOSE_CURSOR
An open cursor is always required to process an SQL statement. The OPEN_CURSOR function returns a cursor ID number, which identifies a valid cursor maintained within Oracle. The procedure CLOSE_CURSOR closes the cursor specified as an argument. Use the INTEGER variable that you have defined as the cursor handle to hold the cursor ID returned by the OPEN_CURSOR function, and pass this variable to the CLOSE_CURSOR procedure as its argument.
MY_CURSOR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.CLOSE_CURSOR(MY_CURSOR);

G. DBMS_SQL.BIND_VARIABLE
In addition to these DBMS_SQL functions and procedures, there is also the BIND_VARIABLE procedure. This procedure allows you to code a placeholder in your SQL statements, to later be filled with the particular value that you choose. The BIND_VARIABLE procedure produces essentially the same result as concatenating a variable into your SQL string. The author has found that the latter method worked fine, with less coding. However, the BIND_VARIABLE procedure might be useful in those instances where the coding for concatenating a variable becomes slightly complex, such as in the case where you are passing literal values into your SQL string. In the author’s experience, use of the BIND_VARIABLE procedure would increase code readability and simplicity in these instances. The BIND_VARIABLE code below would be used to supply the value of the VAL_VAR variable to the ‘x’ placeholder in the following SQL statement.
SELECT CUSTID, NAME FROM CUSTOMER WHERE CUSTID >:x
DBMS_SQL.BIND_VARIABLE(MY_CURSOR, ‘x’, VAL_VAR );
As mentioned above, the same result could be achieved by coding:
SQL_STR VARCHAR2(40);
SQL_STR:=’SELECT CUSTID, NAME FROM CUSTOMER WHERE CUSTID > ‘ || TO_CHAR(VAL_VAR);

Using Dynamic SQL in Forms with Bug Workarounds
Using Dynamic SQL in Forms is very similar to using it in SQL*PLUS or PL/SQL, although there are several differences. The differences arise mainly from the presence of two bugs within Oracle, bugs requiring some workaround code adjustments. These are noted below.
1. PLS-00201 PARSE BUG
The first bug, which produces the error, "PLS-00201: identifier ‘V7’ must be declared," arises when you attempt to run the following PARSE statement from within Forms:
DBMS_SQL.PARSE(MY_CURSOR, ‘SELECT CUSTID FROM CUSTOMER’, DBMS_SQL.V7);
The problem is that the third parameter, the language flag, must be replaced with its actual value, 2. Use 0, 2 and 1 for V6, V7 and NATIVE, respectively. The following worked fine in Forms 4.5.
DBMS_SQL.PARSE(MY_CURSOR, ‘SELECT CUSTID FROM CUSTOMER’, 2);

2. Error 307 COLUMN_VALUE BUG
The second bug concerns the DBMS_SQL.COLUMN_VALUE statement, and produces "Error 307…too many declarations of ‘COLUMN_VALUE’ match this call." This error results when compiling Forms 4.5 Triggers with DBMS_SQL.
The workaround for this problem involves creating a stored function to use in place of the DBMS_SQL.COLUMN_VALUE statement, as follows.
CREATE OR REPLACE
PACKAGE DBMS_SQL_FIX IS
FUNCTION COLUMN_VALUE_CHAR (CID INTEGER, POSITION INTEGER) RETURN VARCHAR2;
FUNCTION COLUMN_VALUE_NUM (CID INTEGER, POSITION INTEGER) RETURN NUMBER;
END;

CREATE OR REPLACE
PACKAGE BODY DBMS_SQL_FIX IS
FUNCTION COLUMN_VALUE_CHAR (CID INTEGER, POSITION INTEGER) RETURN VARCHAR2 IS
VALUE VARCHAR2(2000);
BEGIN
DBMS_SQL.COLUMN_VALUE(CID, POSITION, VALUE);
RETURN VALUE;
END;
FUNCTION COLUMN_VALUE_NUM (CID INTEGER, POSITION INTEGER) RETURN NUMBER IS
VALUE NUMBER;
BEGIN
DBMS_SQL.COLUMN_VALUE(CID, POSITION, VALUE);
RETURN VALUE;
END;
END;
/

Please note the size of 2000 for the variable VALUE and adjust according to the size of the values that you may be returning.
You would then replace the usual COLUMN_VALUE statements as follows. Note that the first example uses CUSTID, a NUMBER variable, and the second example uses NAME, a VARCHAR2 variable. The NUMBER variable requires the use of COLUMN_VALUE_NUM whereas the VARCHAR2 variable requires the use of COLUMN_VALUE_CHAR.
replace
DBMS_SQL.COLUMN_VALUE (my_cursor, 1, CUSTID);
with
CUSTID:= DBMS_SQL_FIX.COLUMN_VALUE_NUM (my_cursor, 1);
And replace
DBMS_SQL.COLUMN_VALUE (my_cursor, 2, NAME);
With
NAME := DBMS_SQL_FIX.COLUMN_VALUE_CHAR (my_cursor, 2);

Other Factors to Consider
An additional consideration when using Dynamic SQL is where to store your code. As with other types of PL/SQL code, it is often preferable to store the code in the database, so that changes can be made quickly and efficiently, without having to change and recompile any of the forms. The author helped create a function in the database, which returned the SQL statement to be run in a VARCHAR2 variable. Once the statement had been constructed, it could then be run in a library, which allowed the results to be copied into form fields using the COPY statement. This way, when the customer inevitably requested changes to the rules involved in the creation of the SQL statement, these changes were very quickly made in the database function.

Oracle SQL Tunning OR Query Tunning

ORACLE SQL TUNING

This document is meant to explain the points to be considered, while
formulating your SQLs. Some of this is meant at the DBA level, but most of
these can be noted, for programmer level.

Before tuning your SQL you should understand the phases of SQL query
processing. There are 5 phases of processing the SQL query :
1) Open
2) Parse
3) Fetch
4) Execute
5) Close
In Open phase, one cursor area (context area) will be opened for your query
In Parse phase, the following processes will take place
i) SQL statement is verified, whether valid or not (Syntax checking).
ii) The tables, columns and privileges are verified (Symantec checking)
iii) A Parse tree is prepared, based on the execution plan
iv) The parse tree into SQL area (reuse to identical query).
In Fetch phase, rows are selected from the corresponding data files and put
into the data block buffer, by server process.
In Execute phase, user process sends the records to the corresponding users.
In Close phase the opened cursor will be closed and temporary memory will be
released.

The following points are to be noted, when you write SQL statements.
# When you write conditions that compare columns with constants, wherever
possible, don't use expressions.
Eg:
Sal>(24000/12) - Not optimized
Sal >2000 - Optimized

# While using LIKE operator, if you are not using wildcards, then don't use
LIKE. Use equality operator instead.
Eg.
desig Like 'DBA' - Not optimized
desig='DBA' - Optimized
desig like 'DB%' - Correct. But if you know the full string, better to use
it.

# When the number of condition in IN operator is less, use OR and =
operator
Eg.
WHERE dno IN (10,20,30) - Not optimized
dno=10 OR dno=20 OR dno=30 - Optimized.

#When you use ANY or ALL or BETWEEN operators, the query optimizer expands
the condition .So, it's better that you expand the condition as far as
possible using OR operator .
Eg.1
Where Sal=ANY(5000,6000) - Not optimized
Where sal=5000 OR sal=6000 - Optimized
Eg2.
Where sal >ALL(5000,6000) - Not optimized
Where sal >5000 AND sal>6000 - Optimized
Eg3.
Where sal BETWEEN 2000 AND 5000 - Not optimized
Where sal >=2000 AND sal<=5000 - Optimized

# Try to avoid using the NOT logical operator as far as possible. Use <> or
!= or ^= relational operators instead.
Eg.
NOT dno = (SELECT dno FROM emp WHERE ename = 'TAYLOR') - Not optimized
deptno <> (SELECT deptno FROM emp WHERE ename = 'TAYLOR') - Optimized

# If your WHERE conditions have more OR operators, then you have to rewrite
the query with SET operators because SET operators execute more efficiently
then OR (Relational Operators).
Eg.
SELECT * FROM emp WHERE job = 'CLERK' OR deptno = 10; - Not optimized

SELECT * FROM emp WHERE job = 'CLERK'
UNION ALL SELECT * FROM emp WHERE deptno = 10 AND job <> 'CLERK';
(Make sure you are having INDEX) - Optimized

# Complex queries have to be changed to join.
Eg.
SELECT * FROM accounts WHERE custno
IN (SELECT custno FROM customers); - Not optimized
SELECT accounts.* FROM accounts, customers
WHERE accounts.custno = customers.custno; - Optimized
(Make sure 'custno' column is the primary key or indexed.)

Optimizer Modes
There are three types of optimizer modes.
1. CHOOSE (Default).
2. COST (Using Resource cost)
3. RULE (Using Access path)

Default optimizer mode is CHOOSE, now Oracle check recourse to set cost
based mode if you are not enabled resource_limit It will set rule based
optimizer mode.

When you set COST based optimizer mode, Oracle considers only CPU resource.
Eg.
Logical reads per call
Logical reads per session
CPU per session
And etc. But you have to set recourse_limit=true in init parameter file to
set cost based optimizer mode.

When you set RULE based optimizer mode, Oracle will never worry about COST
(means resource or I/O cost). It will search the records through the Access
paths. Oracle maintains 15 access paths for each search. Each of these paths
are explained below with the corresponding rank associated with it. Among
these paths, Oracle tries to apply the least rank of the path as possible.
Your queries should apply the least rank of path.


Note:
If you are not able to understand the access path properly, send mail to me.
Why, because unless you are clear with access path, you can't tune SQL
queries those are using RULE based optimizer mode.

ACCESS PATHS (Each number below, is the rank associated with the access
path)

1. Single row returned by ROWID

Eg : SELECT * FROM emp WHERE ROWID = '00000DC5.0000.0001';

2. Single row returned by CLUSTER join
To use this access path, your join tables should be stored in the same
cluster and WHERE clause should be return one only row. To store join tables
in same cluster you have to create cluster for those tables first.

3. Single row returned by hash cluster join, with unique or primary key
To use this access path you have to create a HASH CLUSTER using HASH
function.

4. Single row returned by unique or primary key
To use this access path, the WHERE clause should have all columns in the
unique or primary key with equality condition.

5. Cluster join
To use this access path the join tables should be stored in the same
cluster and WHERE clause should contain conditions that equate each column
of the cluster key in one table with corresponding column in the other
table.

6. Hash cluster key
To use this access path, the WHERE clause should use all columns of hash
cluster key with equality condition.

7. Indexed cluster key
To use this access path, the WHERE clause should use all columns of
indexed cluster key with equality condition.

8. Composite index
To use this access path, the WHERE clause should use all columns of a
composite index in equality conditions combined with AND operators.

9. Single-column index
To use this access path, the WHERE clause should use the columns of one
or more single- column indexes in equality conditions.

10. Bound range search on indexed column
To use this access path, the WHERE clause should contains a condition
that uses either the column of a single-column index or one or more columns
that make up a leading portion of a composite index (using AND)

11. Unbound range search on indexed column.
To use this access path, the WHERE clause should contain one of the
conditions that use either the column of a single-column index or one or
more columns of the leading portion of a composite ndex (Using OR).

12. Sort merge join
To use this access path, the join tables should not be stored together
in a cluster, if the statement's WHERE clause uses columns from each table
in equality conditions.

13. MAX or MIN of indexed column
To use this access path the following conditions must be true.
i) The query uses the MAX or MIN function to select the maximum or minimum
value of either the column of a single-column index or the leading column of
a composite index. The index cannot be a cluster index.
ii) The argument to the MAX or MIN function can be any expression involving
the column, a constant, or the addition operator (+), the concatenation
operation (||), or the CONCAT function.
iii) There are no other expressions in the select list.
iv) The statement has no WHERE clause or GROUP BY clause.

14.ORDER BY on indexed column
To use this access path the following conditions must be true.
i) The query contains an ORDER BY clause that uses either the column of a
single-column index or the leading portion of a composite index. The index
cannot be a cluster index.
ii) There must be a PRIMARY KEY or NOT NULL integrity constraint that
guarantees that at least one of the indexed columns listed in the ORDER BY
clause contains no nulls.
iii) The NLS_SORT parameter is set to BINARY in init parameter file or using
statement ALTER SESSION SET NLS_SORT=BINARY;

15.Full table scan.
This access path is available for any SQL statement, regardless of
its WHERE clause conditions.

When you create a composite index for all the columns which are specified in
WHERE clause, Oracle uses the path composite index (rank 8). This will
increase the performance.
Keep in mind that full table scan is the last rank (15). So, at any point of
time try to avoid the full table scan.

Note:
Even though you are having indexes, your query will never use that index
when you write a query with the following conditions
col1 > col2
col1 < col2
col1 >= col2
col1 <= col2
col1 and col2 are in the same table.
Where Col1 is not null
Where Col1 is null
Where Col1 not in
Where col1!=<expr>
Where col1 like '% pattern'

How to make your JOINS more effective?

When you write a query with two-row source, Oracle performs one of the
following joins. I will take two tables like emp and dept to explain the
joins

1. Nested loops
When you write a query like
select * from emp,dept where emp.dno=dept.dno;
Here EMP is the Driving (outer) table and DEPT is Driven (inner) table.
Oracle performs a full table scan of emp and a rowid scan of DEPT.

2. Sort Merge
Oracle first sorts each row of source to be joined if they have not been
already sorted. Then it performs a full table scan of two tables. But sort
merge joins can perform only for equi-joins.

3. Cluster join
Oracle can perform a cluster join only for an equi-join that equates the
cluster key columns of two tables in the same cluster. Then Oracle performs
a full table scan for dept and cluster scan of emp.

4. Hash join
Here also Oracle performs a full table scan for dept and hash cluster
scan of emp.

Hints
1. When you use OUTER JOIN(+) operator.
The table with the outer join operator must come after the other table in
the condition.
2. When you specify more tables in FROM clause.
The first table in the join order has the most highly ranked access path
(eg. rank 5 table first and rank 7 table second and so on).
3. When you set operators UNION or UNION ALL, as per commitment, both are
same except that UNION uses SORT operation to eliminate the duplicate
records. So, if you are not using any WHERE clause in a compound query and
you are not worrying about duplicate records, then UNION ALL is advisable.

Using Plan Table

Note: The following examples are taken from actual data that is available
for the GECFS-CarPool? application.

Before describing how to use the plan table, it is better to know something
about the examples that are to be used in this document.

There is a table 'T8000_TIC_UNIT_HISTORY' that is created with the synonym
'TIC_UNIT_HISTORY'. To get to know the indexes for this table, use the
following SQL script:

select owner, index_name, table_owner, table_name from all_indexes where
table_name='T8000_TIC_UNIT_HISTORY'

This gives the following result:

OWNER INDEX_NAME TABLE_OWNER TABLE_NAME
OPNSUTIL T8000_TIC_UNIT_HISTORY_X OPNSUTIL T8000_TIC_UNIT_HISTORY

To view the columns that are used in this index, use the following script

select index_name, column_name, column_position from all_ind_columns where
index_name='T8000_TIC_UNIT_HISTORY_X'

This gives the following result

INDEX_NAME COLUMN_NAME COLUMN_POSITION
T8000_TIC_UNIT_HISTORY_X CURR_VIN 1

We can use table 'PLAN_TABLE' to explain the SQL queries. By saying
'explaining SQL queries', it is basically analyzing the queries, so as to
return rows that will say, what is I/O cost of processing the queries, which
index was used for each table in the SQL, which access path was used for the
query, etc.

I shall describe in detail about how to use the PLAN_TABLE, based on the
example above.


The structure of the PLAN_TABLE is as follows:

Name Null? Type
STATEMENT_ID VARCHAR2(30)
TIMESTAMP DATE
REMARKS VARCHAR2(80)
OPERATION VARCHAR2(30)
OPTIONS VARCHAR2(30)
OBJECT_NODE VARCHAR2(128)
OBJECT_OWNER VARCHAR2(30)
OBJECT_NAME VARCHAR2(30)
OBJECT_INSTANCE NUMBER
OBJECT_TYPE VARCHAR2(30)
OPTIMIZER VARCHAR2(255)
SEARCH_COLUMNS NUMBER
ID NUMBER
PARENT_ID NUMBER
POSITION NUMBER
OTHER LONG

>From this, the main fields, which will be used for the purpose of this
document, will be the following fields:
STATEMENT_ID, OPERATION, OPTIONS, OBJECT_NAME, OBJECT_TYPE, ID, and POSITION

We use the following SQL script, which is used to explain the query.

explain plan set statement_id = 'pcs' for
select * from tic_unit_history where curr_vin='1FTFF25G9DKA45510'

Let us see what this means:

The statement_id field is the field that is set by us, to identify a unique
operation. We can use any literal to set the field. While analyzing the rows
returned, we will be using this field to identify the required rows.
The query, that is to be explained, is specified after the 'for' keyword.
So, what this means, in a sort of pseudo-code language is:
"Explain the plan for this query and set the statement_id='pcs' for all the
rows that are returned"

To analyze the rows that are returned, use the following script:

SELECT LPAD(' ',2*(LEVEL-1))||operation||' '||options||' '||object_name
||' '||DECODE(id, 0, 'Cost = '||position) "Query Plan"
FROM plan_table
START WITH id = 0 AND statement_id = 'pcs'
CONNECT BY PRIOR id = parent_id AND statement_id ='pcs';

I've highlighted the field names of the PLAN_TABLE in bold. Note that the
statement_id value given in this script is the same as that given in the
previous script (and it is case-sensitive).

This script will return, the following rows:

Query Plan
SELECT STATEMENT Cost = 2
TABLE ACCESS BY ROWID T8000_TIC_UNIT_HISTORY
INDEX UNIQUE SCAN T8000_TIC_UNIT_HISTORY_X

There's only one column that is returned and 3 rows. The "Query Plan" is the
header.

Now I'll explain each row.

Row 1: This row says that the SQL script, which was to be explained, was a
'SELECT' operation. (Remember that SQL script which we had used for
explaining is :
"select * from tic_unit_history where curr_vin='1FTFF25G9DKA45510'")
It also says that 'Cost = 2'. This means that the I/O cost or resource
access was 2. The unit of measure is unimportant. What is important, though,
is that the lessor the cost, the fast the query is going to execute.
Row 2: This row specifies which table was accessed and which access path was
used.
Row 3: This row specifies the index that was used to scan the table.

Note that, in the SQL script that was to be explained, I have used the
column 'curr_vin' in the WHERE clause. Why I used this specific column is
because, there is only one index for the table 'tic_unit_history', and that
index consists of only one column, which is 'curr_vin'. So, in order that
the query be processed faster, I specifically used the column in the WHERE
clause, which was present in the index. That is why the optimizer used the
index. When I executed the explained SQL script, it took 0.604 seconds to
return 1 record.

Let us explain another SQL query.

We have a table T4310_TAX_LOCATIONS with the synonym TAX_LOCATIONS. The
indexes and the columns in the indexes for this table is as follows :

Index Name Column Name Column Position
I4310_PK_TAX_LOCATIONS TAX_LOC_ID 1
I4310_IX2_TAX_LOCATIONS COUNTRY_CODE 1
I4310_IX2_TAX_LOCATIONS STATE_OR_PROV_ABBR 2
I4310_IX2_TAX_LOCATIONS COUNTY_NAME 3
I4310_IX2_TAX_LOCATIONS CITY_NAME 4
I4310_IX2_TAX_LOCATIONS ZIP_OR_POSTAL_CODE 5
I4310_IX3_TAX_LOCATIONS OWNER_TAX_LOC_ID 1

The script for explaining is as follows:
select * from tax_locations where tax_loc_id between 900000 and 900100

So the explain script will be:
explain plan set statement_id = 'pcs' for
select * from tax_locations where tax_loc_id between 900000 and 900100

Analysis script is the same as before. This will return the following:

Query Plan
SELECT STATEMENT Cost = 29
TABLE ACCESS BY ROWID T4310_TAX_LOCATIONS
INDEX RANGE SCAN I4310_PK_TAX_LOCATIONS

When we execute the query, it takes 0.302 seconds to return 101 records.

Suppose we modify the query a bit. Instead of using BETWEEN, we use the
relational operators <= and >=. The query will be as follows:

select * from tax_locations where tax_loc_id >= 900000 and tax_loc_id
<=900100

The explain script will be:
explain plan set statement_id = 'pcs' for
select * from tax_locations where tax_loc_id >= 900000 and tax_loc_id
<=900100

The analysis will be the same as before. But the query executes in 0.152
seconds to return the same 101 records. Note that the time reduction is 50%.

Let's try an example by using the index 'I4310_IX2_TAX_LOCATIONS'. Consider
the following query:
select * from tax_locations where state_or_prov_abbr='IL' and
county_name='ADAMS' and city_name='QUINCY'

The explain script will be:
explain plan set statement_id = 'pcs' for
select * from tax_locations where state_or_prov_abbr='IL' and
county_name='ADAMS' and city_name='QUINCY'

This analysis script will return:
Query Plan
SELECT STATEMENT Cost = 573
TABLE ACCESS FULL T4310_TAX_LOCATIONS

Note that the cost is 573 units and the access path that was used was Full
Table Scan. The execution time for this query was 2.143 seconds to return 5
records

Let's modify the query so as to include the country_code also, in the WHERE
clause. We must note that, all records in this table have country_code='US'.
So we might consider this insignificant, in the normal case. Anyway, we'll
try to explain the query.

select * from tax_locations where country_code='US' and
state_or_prov_abbr='IL' and county_name='ADAMS' and city_name='QUINCY'

The explain script is as follows:
explain plan set statement_id = 'pcs' for
select * from tax_locations where country_code='US' and
state_or_prov_abbr='IL' and county_name='ADAMS' and city_name='QUINCY'

And the analysis script returns:
Query Plan
SELECT STATEMENT Cost = 3
TABLE ACCESS BY ROWID T4310_TAX_LOCATIONS
INDEX RANGE SCAN I4310_IX2_TAX_LOCATIONS

Now, with the 'insignificant' country_code column, we have reduced the cost
from 573 units to 3 units. This is because, the optimizer found all the
columns in the index, except the last one (ZIP_OR_POSTAL_CODE) in the WHERE
clause of the query. So it used the index