Total Pageviews

Saturday, 8 October 2016

SQL Tuning Set

SQL tuning set (STS) is a database object that contains one or more SQL statements and the  associated execution statistics. You can populate a SQL tuning set from multiple sources, such as SQL recorded in the AWR and SQL in memory, or you can provide specific SQL statements. It’s critical that you be familiar with SQL tuning sets. This feature is used as an input to several of Oracle’s performance tuning and management tools, such as the SQL Tuning Advisor, SQL Plan Management, SQL Access Advisor, and SQL Performance Advisor. The key to understanding is that a SQL tuning set consists of the following:

• One or more SQL statements
• Associated metrics/statistics for each SQL statement

Creating a SQL tuning set:

BEGIN

DBMS_SQLTUNE.CREATE_SQLSET( sqlset_name => 'HIGH_IO', description => 'High disk read tuning set');

 END; /

To check the information you can use the below views:

select id, name, created, statement_count from dba_sqlset;

To check the statements/query associated with a sqltune set

SELECT sqlset_name, elapsed_time, cpu_time, buffer_gets, disk_reads, sql_text FROM dba_sqlset_statements;

Drop a sqlset:

EXEC DBMS_SQLTUNE.DROP_SQLSET(sqlset_name => 'MY_TUNING_SET' );

Delete specific statements from the sqlset:

BEGIN

DBMS_SQLTUNE.DELETE_SQLSET(sqlset_name => 'IO_STS',basic_filter => 'disk_reads < 2000000');

END;/

Delete complete sqlset:

exec DBMS_SQLTUNE.DELETE_SQLSET(sqlset_name => 'IO_STS');












Populating SQL Tuning Set from High-Resource SQL in AWR:

Problem

You want to create a SQL tuning set and populate it with the top I/O-consuming SQL statements found in the AWR.

Solution

Use the following steps to populate a SQL tuning set from high resource-consuming statements in the AWR:

1. Create a SQL tuning set object.
2. Determine begin and end AWR snapshot IDs.
3. Populate the SQL tuning set with high-resource SQL found in AWR.
4. Load the sqltune set with the SQL found in step 3rd

Step 1: Create a SQL Tuning Set Object

Create a SQL tuning set. This next bit of code creates a tuning set named IO_STS:

BEGIN
dbms_sqltune.create_sqlset( sqlset_name => 'IO_STS' description => 'STS from AWR');
END;
/
Step 2: Determine Begin and End AWR Snapshot IDs

If you’re unsure of the available snapshots in your database, you can run an AWR report or select the SNAP_ID from DBA_HIST_SNAPSHOTS:

select snap_id, begin_interval_time from dba_hist_snapshot order by 1;

Step 3 and 4th : Populate the SQL Tuning Set with High-Resource SQL Found in AWR and load them in the sqlset

Now the SQL tuning set is populated with the top 15 SQL statements ordered by disk reads. The begin and end AWR snapshot IDs are 26800 and 26900 respectively:

DECLARE
base_cur dbms_sqltune.sqlset_cursor;
BEGIN
OPEN base_cur FOR
SELECT value(x)
FROM table(dbms_sqltune.select_workload_repository(
26800,26900, null, null,'disk_reads',
null, null, null, 15)) x;
--

dbms_sqltune.load_sqlset(
sqlset_name => 'IO_STS',
populate_cursor => base_cur);
END;
/

The prior code populates the top 15 SQL statements contained in the AWR ordered by disk reads.The DBMS_SQLTUNE.SELECT_WORKLOAD_REPOSITORY function is used to populate a PL/SQL cursor with AWR
information based on a ranking criterion. Next the DBMS_SQLTUNE.LOAD_SQLSET procedure is used to populate the SQL tuning set using the cursor as input.

You can view the details of the SQL tuning set (created in the “Solution” section) via this query:

SELECT
sqlset_name
,elapsed_time
,cpu_time
,buffer_gets
,disk_reads
,sql_text
FROM dba_sqlset_statements
WHERE sqlset_name = 'IO_STS';

DBMS_SQLTUNE.SELECT_WORKLOAD_REPOSITORY
Before populating a SQL tuning set, you want to view high-load SQL statements in the AWR. You want to eventually use SQL contained in the AWR as input for populating a SQL tuning set. Basic filter ==> SQL predicate to filter SQL statements from workload; and ranking_measure==> Order by clause on selected SQL statement(s), such as elapsed_time, cpu_time, buffer_gets, disk_reads .result_limit to limit the number of rows returned as output.



SELECT  sql_id ,substr(sql_text,1,20),disk_reads ,cpu_time,elapsed_time
FROM
 table(DBMS_SQLTUNE.SELECT_WORKLOAD_REPOSITORY(
begin_snap => 21730 ,
end_snap => 22900
,basic_filter => 'parsing_schema_name <> ''SYS'''
,ranking_measure1 => 'disk_reads' ,
result_limit => 10)
)
ORDER BY disk_reads DESC;


















Populating a SQL Tuning Set from Resource-Consuming SQL in Memory

You want to populate a tuning set from high resource-consuming SQL statements that are currently in the memory.
Use the DBMS_SQLTUNE.SELECT_CURSOR_CACHE function to populate a SQL tuning set with statements currently in memory. This example creates a tuning set and populates it with high-load resource consuming statements not belonging to the SYS schema and having disk reads greater than 1,000,000:

-- Create the tuning set

EXEC DBMS_SQLTUNE.CREATE_SQLSET('HIGH_DISK_READS');

-- populate the tuning set from the cursor cache

DECLARE
cur DBMS_SQLTUNE.SQLSET_CURSOR;
BEGIN
OPEN cur FOR
SELECT VALUE(x)
FROM table(
DBMS_SQLTUNE.SELECT_CURSOR_CACHE(
'parsing_schema_name <> ''SYS'' AND disk_reads > 1000000',
NULL, NULL, NULL, NULL, 1, NULL,'ALL')) x;
--
DBMS_SQLTUNE.LOAD_SQLSET(sqlset_name => 'HIGH_DISK_READS', populate_cursor => cur);
END; /

In the prior code, notice that the SYS user is bookended by sets of two single quotes (not double quotes). The SELECT_CURSOR_CACHE function loads the SQL statements into a PL/SQL cursor, and the LOAD_SQLSET procedure populates the SQL tuning set with the SQL statements.

The DBMS_SQLTUNE.SELECT_CURSOR_CACHE function allows you to extract from memory SQL statements and associated statistics into a SQL tuning set. The procedure allows you to filter SQL statements by various resource-consuming criteria, such as the following:

·         ELAPSED_TIME
·         CPU_TIME
·         BUFFER_GETS
·         DISK_READS
·         DIRECT_WRITES
·         ROWS_PROCESSED

This allows you a great deal of flexibility on how to filter and populate the SQL tuning set.

use DBMS_SQLTUNE.SELECT_CURSOR_CACHE function to retrieve high resource-usage SQL from memory. The result set retrieved by this PL/SQL function can be used as input for populating SQL tuning sets

SELECT sql_id ,substr(sql_text,1,20)
,disk_reads ,cpu_time ,elapsed_time ,buffer_gets ,parsing_schema_name
FROM table(
DBMS_SQLTUNE.SELECT_CURSOR_CACHE( basic_filter => 'parsing_schema_name <> ''SYS'''
,ranking_measure1 => 'cpu_time' ,result_limit => 10
));



Populating SQL Tuning Set with All SQL in Memory

Solution

Use the DBMS_SQLTUNE.CAPTURE_CURSOR_CACHE_SQLSET procedure to efficiently capture all of the SQL
currently stored in the cursor cache (in memory). This example creates a SQL tuning set named PROD_WORKLOAD and then populates by sampling memory for 3,600 seconds (waiting 20 seconds between each polling event):

BEGIN

-- Create the tuning set
DBMS_SQLTUNE.CREATE_SQLSET(
sqlset_name => 'PROD_WORKLOAD'
,description => 'Prod workload sample');
--
DBMS_SQLTUNE.CAPTURE_CURSOR_CACHE_SQLSET(
sqlset_name => 'PROD_WORKLOAD'
,time_limit => 3600
,repeat_interval => 20);
END;
/

The DBMS_SQLTUNE.CAPTURE_CURSOR_CACHE_SQLSET procedure allows you to poll for queries and memory
and use any queries found to populate a SQL tuning set. This is a powerful technique that you can use when it’s required to capture a sample set of all SQL statements executing.
You have a great deal of flexibility on instructing DBMS_SQLTUNE.CAPTURE_CURSOR_CACHE_SQLSET to capture SQL statements in memory (see Table 11-7 for details on all parameters). For example, you can instruct the procedure to capture a cumulative set of statistics for each SQL statement by specifying a

CAPTURE_MODE of DBMS_SQLTUNE.MODE_ACCUMULATE_STATS.
BEGIN
DBMS_SQLTUNE.CAPTURE_CURSOR_CACHE_SQLSET(
sqlset_name => 'PROD_WORKLOAD'
,time_limit => 60
,repeat_interval => 10
,capture_mode => DBMS_SQLTUNE.MODE_ACCUMULATE_STATS);
END;
/
This is more resource-intensive than the default settings, but produces more accurate statistics for each SQL statement.




Transport SQL Tuning Set from Production to TEST Environment:

You’ve identified some resource-intensive SQL statements in a production environment. You want to transport these statements and associated statistics to a test environment, where you can tune the statements without impacting production.

Solution:
The following steps are used to copy a SQL tuning set from one database to another:

1. Create a staging table in source database.
2. Populate the staging table with STS data.
3. Copy the staging table to the destination database.
4. Unpack the staging table in the destination database.

Step 1: Create a Staging Table in the Source Database

Use the DBMS_SQLTUNE.CREATE_STGTAB_SQLSET procedure to create a table that will be used to contain the
SQL tuning set metadata. This example creates a table named STS_TABLE:

BEGIN
dbms_sqltune.create_stgtab_sqlset(
table_name => 'STS_TABLE'
,schema_name => 'MV_MAINT');
END;
/
Step 2: Populate Staging Table with STS Data

Now populate the staging table with STS metadata using DBMS_SQLTUNE.PACK_STGTAB_SQLSET:

BEGIN
dbms_sqltune.pack_stgtab_sqlset(
sqlset_name => 'IO_STS'
,sqlset_owner => 'SYS'
,staging_table_name => 'STS_TABLE'
,staging_schema_owner => 'MV_MAINT');
END;
/

If you’re unsure of the names of the STS you want to transport, run the following query to get the details:

SELECT name, owner, created, statement_count FROM dba_sqlset;

Step 3: Copy the Staging Table to the Destination Database

You can copy the table from one database to the other via Data Pump, the old exp/imp utilities, or by using a database link. This example creates a database link in the destination database and then copies the table from the source database:

create database link source_db connect to mv_maint identified by foo using 'source_db';

In the destination database, the table can be copied directly from the source with the CREATE TABLE AS SELECT statement:

SQL> create table STS_TABLE as select * from STS_TABLE@source_db;

Step 4: Unpack the Staging Table in the Destination Database

Use the DBMS_SQLTUNE.UNPACK_STGTAB_SQLSET procedure to take the contents of the staging table and populate the data dictionary with the SQL tuning set metadata. This example unpacks all SQL tuning sets contained within the staging table:


BEGIN
DBMS_SQLTUNE.UNPACK_STGTAB_SQLSET(
sqlset_name => '%'
,replace => TRUE
,staging_table_name => 'STS_TABLE');
END;
/

How It Works

A SQL tuning set consists of one or more queries and corresponding execution statistics. You will occasionally have a need to copy a SQL tuning set from one database to another. For example, you might be having performance problems with a production database but want to capture and move the top resource-consuming statements to a test database where you can diagnose the SQL (within the STS) without impacting production.

Keep in mind that an STS can be used as input for any of the following tools:

·         SQL Tuning Advisor
·         SQL Access Advisor
·         SQL Plan Management
·         SQL Performance Analyzer

The prior tools are used extensively to troubleshoot and test SQL performance. Transporting a SQL tuning set from one environment to another allows you to use these tools in a testing or development environment.



SQL Profile

Creating and Accepting a SQL Profile
Problem

You have a poorly performing query, and you want to get advice from the SQL Tuning Advisor. You realize that the SQL Tuning Advisor may recommend that a SQL profile be applied to the problem query as part of the tuning recommendation.

Solution

Run the SQL Tuning Advisor for the problem query. Keep in mind that the SQL Tuning Advisor may or may not recommend a SQL profile as a solution for performance issues. To run the SQL Tuning Advisor manually, perform the following steps:

1. Use DBMS_SQLTUNE to create a tuning task.
2. Execute the tuning task.
3. Generate the tuning advice report.
4. If SQL profile is part of the tuning advice output, then create and accept.
Step 1: Use DBMS_SQLTUNE to Create a Tuning Task
The first step is to create a tuning task that is associated with the problem SQL statement. In the following code, the SQL text is hard-coded as input to the tune_sql variable:

DECLARE
tune_sql CLOB;
tune_task VARCHAR2(30);
BEGIN
tune_sql := 'select count(*) from mgmt_db_feature_usage_ecm2';
tune_task := DBMS_SQLTUNE.CREATE_TUNING_TASK(
sql_text => tune_sql,user_name => 'STAGING',scope => 'COMPREHENSIVE' ,time_limit => 60,task_name => 'TUNE1',description => 'Calling SQL Tuning Advisor for one statement' );
END;
/
The prior code is placed in a file named sqltune.sql, and executed as follows:
SQL> @sqltune.sql
Step 2: Execute the Tuning Task
This step runs the SQL Tuning Advisor to generate advice regarding any queries associated with the tuning task (created in step 1):

SQL> exec dbms_sqltune.execute_tuning_task(task_name=>'TUNE1');

Step 3: Run Tuning Advice Report

Now use DBMS_SQLTUNE to extract any tuning advice generated in step 2:

set long 10000
set longchunksize 10000
set lines 132
set pages 200
select dbms_sqltune.report_tuning_task('TUNE1') from dual;
For this example, the SQL Tuning Advisor recommends creating a SQL profile. Here is a snippet from the output that contains the recommendation and the code required to create the SQL profile:
Recommendation (estimated benefit: 86.11%)
------------------------------------------
- Consider accepting the recommended SQL profile to use parallel execution for this statement.

execute dbms_sqltune.accept_sql_profile(task_name => 'TUNE1', task_owner
=> 'SYS', replace => TRUE, profile_type =>DBMS_SQLTUNE.PX_PROFILE);

-------------------------------------------
Executing this query parallel with DOP 8 will improve its response time 86.11% over the original plan. However, there is some cost in enabling parallel execution...

Step 4: Create and Accept SQL Profile

To actually create the SQL profile, you need to run the code recommended by the SQL Tuning Advisor (from step 3)—for example:

begin
-- This is the code from the SQL Tuning Advisor
dbms_sqltune.accept_sql_profile(
task_name => 'TUNE1',
task_owner => 'SYS',
replace => TRUE,
FORCE_MATCH=>TRUE
profile_type => DBMS_SQLTUNE.PX_PROFILE);
--
end;
/
When the prior code is run, it creates and enables the SQL profile. Now whenever the associated SQL query is executed, the SQL profile will be considered by the optimizer when formulating an execution plan.

How It Works
The only Oracle-supported method for creating a SQL profile is to run the SQL Tuning Advisor and if recommended, create a SQL profile using the Tuning Advisor’s output. In other words, the SQL Tuning Advisor determines if a SQL profile will help, and if so generates the code required to create a SQL profile for a given query.
The “Solution” section detailed how to manually run the SQL Tuning Advisor. Keep in mind that as of Oracle Database 11g, this tuning task job automatically runs on a regularly scheduled basis.

You can easily review the output of the automatic tuning job via this query:

SQL> SELECT DBMS_AUTO_SQLTUNE.REPORT_AUTO_TUNING_TASK FROM DUAL;

We recommend that you review the output of the automatic tuning job on a regular basis. The SQL Tuning Advisor will provide the code to create and accept SQL profiles as part of the output.

The FORCE_MATCH parameter of ACCEPT_SQL_PROFILE requires further explanation. Recall that a SQL profile is associated with a SQL statement. The SQL statement is identified via a hash function (SQL signature). The hash function is generated after converting the SQL text and removing extra white space.

When setting FORCE_MATCH to TRUE, this additionally normalizes literal values into bind values. This is similar to the algorithm generated via the FORCE option of the CURSOR_SHARING database initialization parameter.
For example, with FORCE_MATCH set to TRUE, the following two SQL statements will generate the same SQL signature:
SQL> select value from my_table where value = 'AA';
SQL> select value from my_table where value = 'bb';

This allows SQL statements that use literal values to share the same SQL profile. If there is a combination of literal values and bind variables in a SQL statement, then literal values are not normalized.

Automatically Accepting SQL Profiles

Use the DBMS_AUTO_SQLTUNE.SET_AUTO_TUNING_TASK_PARAMETER procedure to enable the automatic
acceptance of SQL profiles recommended by the Automatic SQL Tuning task—for example:

BEGIN
DBMS_AUTO_SQLTUNE.SET_AUTO_TUNING_TASK_PARAMETER(
parameter => 'ACCEPT_SQL_PROFILES', value => 'TRUE');
END;
/

Displaying SQL Profile Information

Use the DBA_SQL_PROFILES view to display information about SQL profiles. Here’s an example that selects the most interesting columns:

SQL> select name, type, status, sql_text from dba_sql_profiles;

Disabling a SQL Profile

First verify the name of the SQL profile that you want to disable:

SQL> select name, status from dba_sql_profiles;

Here’s a partial snippet of the output:
NAME STATUS
------------------------------ --------
SYS_SQLPROF_012eda58a1be0001 ENABLED

Now use the DBMS_SQLTUNE.ALTER_SQL_PROFILE procedure to modify the status of the profile to disabled:

BEGIN
DBMS_SQLTUNE.ALTER_SQL_PROFILE(
name => 'SYS_SQLPROF_012eda58a1be0001',
attribute_name => 'STATUS',
value => 'DISABLED');
END;

Dropping a SQL Profile

SQL> exec dbms_sqltune.drop_sql_profile('SYS_SQLPROF_012edef0d0a70002');

Moving a SQL Profile

Problem
You have a test database and want to extract all of the SQL profiles from the test database and move them to a production database.

Solution

Listed next are the steps involved with transporting a SQL profile from one database to another:

1. Create a staging table.
2. Populate the staging table.
3. Move the table from the source database to the destination database (Data Pump or database link).
4. On the destination database, extract information from the staging table to populate the data dictionary with SQL profile
information.

Step 1: Create a Staging Table
Use the DBMS_SQLTUNE.CREATE_STGTAB_SQLPROF procedure to create the staging table. This example creates a
table named PROF_STAGE owned by the MV_MAINT user:

BEGIN
dbms_sqltune.create_stgtab_sqlprof(
table_name => 'PROF_STAGE',
schema_name => 'MV_MAINT' );
END;
/
Step 2: Populate the Staging Table
Use the DBMS_SQLTUNE.PACK_STGTAB_SQLPROF procedure to populate the table created in step 1 with SQL
profile information. This example populates the table with information regarding a specific SQL profile:

BEGIN
dbms_sqltune.pack_stgtab_sqlprof(
profile_name => 'SYS_SQLPROF_012edf84806e0004',
staging_table_name => 'PROF_STAGE',
staging_schema_owner => 'MV_MAINT' );
END;
/

Step 3: Copy the Staging Table to the Destination Database

You can copy the table from one database to the other via Data Pump, the old exp/imp utilities, or by using a database link. This example creates a database link in the destination database and then copies the table from the source database:

create database link source_db connect to mv_maint identified by foo using 'source_db';

Once the database link has been created, the table can be copied directly from the source with the CREATE TABLE...AS SELECT statement:

SQL> create table PROF_STAGE as select * from PROF_STAGE@source_db;

Step 4: Load the Contents of the Staging Table into the Destination Database
Now in the destination database, unpack the table to load profile information into the database:

BEGIN
DBMS_SQLTUNE.UNPACK_STGTAB_SQLPROF(
replace => TRUE,
staging_table_name => 'PROF_STAGE');
END;
/
If no profile name is specified, the default is the % wildcard character (meaning all profiles in the table will be loaded into the destination database).




logminer

EXECUTE DBMS_LOGMNR_D.BUILD(DICTIONARY_FILENAME =>'dictionary.ora', DICTIONARY_LOCATION => 'E:\home\oracle\oradata\chicago');
EXECUTE DBMS_LOGMNR.ADD_LOGFILE( LOGFILENAME => 'E:\home\oracle\oradata\chicago\REDO01.log', OPTIONS => dbms_logmnr.NEW);
EXECUTE DBMS_LOGMNR.ADD_LOGFILE( LOGFILENAME => 'E:\home\oracle\oradata\chicago\REDO02.log', OPTIONS => dbms_logmnr.NEW);
EXECUTE DBMS_LOGMNR.ADD_LOGFILE( LOGFILENAME => 'E:\home\oracle\oradata\chicago\REDO03.log', OPTIONS => dbms_logmnr.NEW);
EXECUTE DBMS_LOGMNR.START_LOGMNR( DICTFILENAME =>'E:\home\oracle\oradata\chicago\dictionary.ora');

Execution Plan

If the query is running slower, then it is probably using the bad plan. This can be checked by running below query and observing the plan number for the latest plan.

alter session set nls_date_format='DD-MON-YYYY HH24:MI:SS';
select plan_hash_value, child_number, TIMESTAMP from v$sql_plan where sql_id ='49fhsvm5z337y' order by 3;

This can usually be resolved by flushing the query from the cache by using the below steps:

select address, hash_value from v$sqlarea where sql_id='49fhsvm5z337y';

Use the values from above query in the below purge statement:

exec sys.dbms_shared_pool.purge('C000000EF6D74030,3421605118','C');

Following this, the query should use the correct plan:

SQL> select plan_hash_value, child_number, TIMESTAMP from v$sql_plan where sql_id ='49fhsvm5z337y' order by 3;

PLAN_HASH_VALUE CHILD_NUMBER TIMESTAMP
--------- ------- ------------
     1784340712 0 01-DEC-2012 04:55
     1784340712 0 01-DEC-2012 04:55
     1784340712 0 01-DEC-2012 04:55
     1784340712 0 01-DEC-2012 04:55
If the above flush does not work, then another possible option could be to  analyze the table with a 100% estimate:

Begin
 dbms_stats.gather_table_stats(ownname=>'EPUSER1',
tabname=>'LISTING_CUR_DIM',estimate_percent=>100,
method_opt=>' FOR ALL INDEXED COLUMNS SIZE AUTO', degree=>2,granularity=>'DEFAULT',cascade=>TRUE,no_invalidate=>FALSE);
END;
Also, here's a very helpful query to check the execution history of the SQL along with the execution plan:

set pages 100 lines 204 wrap off
column "Parsing Schema" format a15
column "ms Per Exec" format 9999999.99
column "Total Execs" format 999999999
column "Gets Per Exec" format 99999999
column "CPU %" format 99999999999
column "Rows Per Exec" format 99999999
column "Disk Reads Per Exec" format 99999999
Column "Start Time" format a18
Column "End Time" format a18
column " TPS" format 99999
select  a.snap_id "Snap Id", parsing_schema_name "Parsing Schema", plan_hash_value,
        to_char(min(b.begin_interval_time),'MM/DD/YYYY HH24:MI') "Start Time", to_char(max(b.end_interval_time),'MM/DD/YYYY HH24:MI') "End Time",
        a.sql_id, round(sum(elapsed_time_delta)/1000/sum(executions_delta),2) "ms Per Exec", sum(a.executions_delta) "Total Execs",
        round(sum(buffer_gets_delta)/sum(executions_delta)) "Gets Per Exec",  
    round(sum(rows_processed_delta)/sum(executions_delta)) "Rows Per Exec"
from ( select snap_id, sql_id, instance_number,parsing_schema_name, plan_hash_value, executions_delta, elapsed_time_delta,
    buffer_gets_delta, cpu_time_delta, disk_Reads_delta,rows_processed_delta
from dba_hist_sqlstat x
where sql_id in ('49fhsvm5z337y')
and   x.snap_id between  1 and 100000000) a, dba_hist_snapshot b
where executions_delta >0
and   a.snap_id = b.snap_id
and   a.instance_number=b.instance_number
group by a.snap_id, a.sql_id, parsing_schema_name, plan_hash_Value
order by 2,1;

The output of the above query will be as below. As can be seen from the execution history, when the plan_hash_value is 1784340712, the query gets executed in < 10 ms per exec and performs <=6 block reads per execution ("Gets Per Exec"). Because of the low elapsed time for the query, there are also more executions of the query. With the bad plan, the execution time is very high and "gets per exec" also indicates there are more data blocks being read.


set pages 100 lines 204 wrap off
column "Parsing Schema" format a15
column "ms Per Exec" format 9999999.99
column "Total Execs" format 999999999
column "Gets Per Exec" format 999999999
column "CPU %" format 99999999999
column "Rows Per Exec" format 99999999
column "Disk Reads Per Exec" format 99999999
Column "Start Time" format a18
Column "End Time" format a18
column " TPS" format 99999
select  a.snap_id "Snap Id", parsing_schema_name "Parsing Schema", plan_hash_value,
        to_char(min(b.begin_interval_time),'MM/DD/YYYY HH24:MI') "Start Time", to_char(max(b.end_interval_time),'MM/DD/YYYY HH24:MI') "End Time",
        a.sql_id, round(sum(elapsed_time_delta)/1000/sum(executions_delta),2) "ms Per Exec", sum(a.executions_delta) "Total Execs",
        round(sum(buffer_gets_delta)/sum(executions_delta)) "Gets Per Exec",
    round(sum(rows_processed_delta)/sum(executions_delta)) "Rows Per Exec"
from ( select snap_id, sql_id, instance_number,parsing_schema_name, plan_hash_value, executions_delta, elapsed_time_delta,
    buffer_gets_delta, cpu_time_delta, disk_Reads_delta,rows_processed_delta
from dba_hist_sqlstat x
where sql_id in ('&sid')
and   x.snap_id between  1 and 100000000) a, dba_hist_snapshot b
where executions_delta >0
and   a.snap_id = b.snap_id
and   a.instance_number=b.instance_number
group by a.snap_id, a.sql_id, parsing_schema_name, plan_hash_Value
order by 2,1;

Create a SQl Tuning Task

DECLARE
tune_task VARCHAR2(30);
tune_sql CLOB;
BEGIN
tune_task := DBMS_SQLTUNE.CREATE_TUNING_TASK(
sql_id => 'xxxxxxxxxxxx'
,begin_snap => 9097
,end_snap => 9150
,task_name => 'tune_sql_xxxx'
);
END;
/


exec dbms_sqltune.execute_tuning_task(task_name => 'tune_sql_xxx');

set long 10000000000 longchunksize 100000000;
set linesize 2000 pagesize 50000;

select dbms_sqltune.report_tuning_task('tune_sql_xxx') from dual;