Working with Oracle’s Time Model Data

13 01 2010

January 13, 2010

(Forward to the Next Post in the Series)

As you might be aware several interesting performance views were added in Oracle Database 10.1 that help determine why a session is processing slower than expected.  The performance views offer data far beyond that of V$SYSSTAT, V$SESSTAT and the wait event interface that has been a part of Oracle since version 7.  One such view is the instance-wide V$SYS_TIME_MODEL view, and its associated session level V$SESS_TIME_MODEL view.  Oracle Database 10.1 also added V$OSSTAT, which helps determine how busy the server’s CPUs are as a whole, rather than just within the database instance.

It is easy to query the V$SYS_TIME_MODEL view to output just the statistic name and the value of that statistic, as shown below:

However, there are a couple of problems with just querying the view:

  • It is hard to see the relationship between the various statistics.  Some of the statistics act as “parent” statistics for other statistic, essentially further dividing the parent statistic’s value into multiple sub-statistics.
  • The numbers are very large, making it easy to lose the scale of a particular problem.
  • The numbers represent totals since the database was last started.

A much more logical way to analyze the statistics is to arrange those statistics into a tree structure, and then to divide the values by 1,000,000 to convert the values to seconds.  For example, something like this:

Making the above conversion could be a little time consuming if the task must be performed frequently.  Thus, we need to find a way to automate the process.

Maybe something like this that not only shows the output from V$SYS_TIME_MODEL, but also V$OSSTAT and V$SYSTEM_EVENT:

If you have read many of my blog articles, you will probably quickly realize that the above is not yet good enough.  The values shown still tell the accumulated statistic values since the database was brought online, which was probably a short time before these statistics were captured.  The above shows that the server’s CPUs were on average about 33% busy at the operating system, system-wide level, with most of that time spent executing user code, rather than operating system kernel code.  The elapsed time for Oracle’s background tasks was just over 53 seconds, with 5.29 of those seconds spent running on the CPU.  Let’s see, 6,029 seconds busy, 5.29 + 2,362 seconds in the database instance – something else is running on the server consuming CPU cycles.  The DB Time component, which captures the accumulated CPU usage and wait events for all user sessions, totalled 2,619 seconds, of which 2,362 seconds were spent actively running on the server’s CPUs.

Nice, but not good enough, to determine if there is a performance problem, we need to constrain the statistics to a specific time interval, possibly a minute, something like the following:

Notice in the above that the server’s CPUs in this time period were roughly 70% busy, suggesting that careful time scoping will make a significant difference.  Nice, but still not good enough.

We need to be able to drill into the session level detail to see which sessions are the largest contributors to the V$SYS_TIME_MODEL statistics if we want to do something useful with the statistics, something like this:

So, how was the above output created?  As you can probably tell, the output is displayed in Internet Explorer, which probably means that it was generated by a web server of some sort.  But, I have not yet written an article on this blog that shows how to query the database from a web server…

An explanation of how the web page was created will follow in a later blog post.





Mining Your Own Business, Is Supplemental Logging Enabled?

12 01 2010

January 12, 2010

A couple years ago the following question was asked in the comp.databases.oracle.server Usernet group:
http://groups.google.com/group/comp.databases.oracle.server/browse_thread/thread/7d4bfb37a4fd4a33/

How can I determine if SUPPLEMENTAL LOGGING as been enable at the database level?

Done by this command:

ALTER DATABASE ADD SUPPLEMENTAL LOG DATA  (PRIMARY KEY, UNIQUE, FOREIGN KEY) COLUMNS;

The original poster did not mention why he wanted to check the status of supplemental logging, but I suspect that he wanted to use LogMiner.

The response that I provided to the OP follows:

Take a look at the output of the following:

SELECT
  SUPPLEMENTAL_LOG_DATA_MIN,
  SUPPLEMENTAL_LOG_DATA_PK,
  SUPPLEMENTAL_LOG_DATA_UI
FROM
  V$DATABASE;

SUPPLEME SUP SUP
-------- --- ---
NO       NO  NO

Now, execute your statement:

ALTER DATABASE ADD SUPPLEMENTAL LOG DATA  (PRIMARY KEY, UNIQUE, FOREIGN KEY) COLUMNS;

Then check again: 

SELECT
  SUPPLEMENTAL_LOG_DATA_MIN,
  SUPPLEMENTAL_LOG_DATA_PK,
  SUPPLEMENTAL_LOG_DATA_UI
FROM
  V$DATABASE;

SUPPLEME SUP SUP
-------- --- ---
IMPLICIT YES YES

From the Oracle Database Reference 10g Release 2 pg 6-54 (PDF page 670):
“SUPPLEMENTAL_LOG_DATA_MIN VARCHAR2(8) Ensures that LogMiner (and any products building on LogMiner technology) will have sufficient information to support chained rows and various storage arrangements such as cluster tables:

  •  NO – None of the database-wide supplemental logging directives are enabled 
  • IMPLICIT – Minimal supplemental logging is enabled because all or a combination of primary key, unique key, and foreign key supplemental logging is enabled
  • YES – Minimal supplemental logging is enabled through an ALTER DATABASE ADD SUPPLEMENTAL LOG DATA statement

See Also: Oracle Database SQL Reference for additional information about the ALTER DATABASE ADD SUPPLEMENTAL LOG DATA statement

SUPPLEMENTAL_LOG_DATA_PK
VARCHAR2(3) For all tables with a primary key, indicates whether all columns of the primary key are placed into the redo log whenever an update is performed (YES) or not (NO)
See Also: Oracle Database SQL Reference for more information about the ALTER DATABASE ADD SUPPLEMENTAL LOG supplemental_id_key_clause statement

SUPPLEMENTAL_LOG_DATA_UI
VARCHAR2(3) For all tables with a unique key, indicates whether all other columns belonging to the unique key are placed into the redo log if any of the unique key columns are modified (YES) or not (NO) See Also: Oracle Database SQL Reference for more information about the
ALTER DATABASE ADD SUPPLEMENTAL LOG supplemental_id_key_clause statement”





Select From or Update a Database Table Based on the Contents of an Excel Spreadsheet

12 01 2010

January 12, 2010

Let’s say that there is an Excel spreadsheet containing a list of customer order IDs in column A, and you would like to query an Oracle database using the value in column A, and then display a message on the screen showing the results of the query.  The following macro code will do just that:

Sub CheckSpreadsheet() 
    Dim dbMyDB As New ADODB.Connection 
    Dim snpData As New ADODB.Recordset 
    Dim intLastRowChecked 
    Dim intFoundFirstBlank 
    Dim intResult As Integer 
    Dim intColumn 
    Dim strColumn 
    Dim strFilename 
    Dim strWorkbookname 
    Dim strSheet 
    Dim strExcelValue 
    Dim strSQL 
    Dim strMessage
    'You must create a reference to Microsoft ActiveX Data Objects (Tools menu)
    'Make sure that we don't crash - will look ugly if our macro crashes
    On Error Resume Next
    'Replace MyODBCConnection with an ODBC connection name, MyUserName with a database user name and MyPassword with the user's password 
    dbMyDB.ConnectionString = "Data Source=MyODBCConnection;User ID=MyUserName;Password=MyPassword;"
    dbMyDB.ConnectionTimeout = 40 
    dbMyDB.CursorLocation = adUseClient 
    dbMyDB.Open
    strWorkbookname = Right(ActiveWorkbook.FullName, Len(ActiveWorkbook.FullName) -InStrRev(ActiveWorkbook.FullName, "\")) 
    strSheet = ActiveSheet.Name
    intLastRowChecked = 1 'Set to skip the first row 
    intColumn = 65  'Column A 
    strColumn = Chr(intColumn)
    Do While intFoundFirstBlank = False 
        intLastRowChecked = intLastRowChecked + 1
        'Read the value from the spreadsheet 
        strExcelValue = Format(Workbooks(strWorkbookname).Worksheets(strSheet).Range(strColumn & Format(intLastRowChecked)).Value)
        If strExcelValue = "" Then 
            intFoundFirstBlank = True 
        Else 
            'Could perform an INSERT statement rather than a SELECT statement 
            strSQL = "SELECT" & vbCrLf 
            strSQL = strSQL & "  LINE_NO," & vbCrLf 
            strSQL = strSQL & "  PART_ID," & vbCrLf 
            strSQL = strSQL & "  ORDER_QTY," & vbCrLf 
            strSQL = strSQL & "  DESIRED_SHIP_DATE" & vbCrLf 
            strSQL = strSQL & "FROM" & vbCrLf 
            strSQL = strSQL & "  CUST_ORDER_LINE" & vbCrLf 
            strSQL = strSQL & "WHERE" & vbCrLf 
            strSQL = strSQL & "  CUST_ORDER_ID='" & strExcelValue & "'" & vbCrLf 
            strSQL = strSQL & "ORDER BY" & vbCrLf 
            strSQL = strSQL & "  DESIRED_SHIP_DATE"
            snpData.Open strSQL, dbMyDB
            Do While Not snpData.EOF 
                strMessage = strExcelValue & "/" & Format(snpData("line_no")) & " " & Format(snpData("desired_ship_date"), "m/d/yyyy") & _
                             " " & snpData("part_id") & " Qty " & Format(snpData("order_qty")) 
                MsgBox strMessage
                snpData.MoveNext 
            Loop 
            snpData.Close 
        End If 
    Loop
    Set snpData = Nothing 
    dbMyDB.Close 
    Set dbMyDB = Nothing 
End Sub

Note that there are a couple minor issues with the above script:

  • The script runs until it finds a blank cell in column A, rather than using an Excel feature to identify the bounds of the range.
  • The script requires an ODBC (32 bit) to be created on the computer.  Search the other articles on this blog to see how to establish a connection to the database without creating an ODBC connection.
  • The script does not use bind variables.  Search the other articles on this blog to see how to implement bind variables in an Excel macro.

By changing the script slightly, the SELECT statement could be modified to be an UPDATE statement, allowing an easy method to update the database based on data contained in the Excel spreadsheet.





Explain Plan Lies, Autotrace Lies, TKPROF Lies, What is the Plan?

11 01 2010

January 11, 2010

As some of you might be aware, EXPLAIN PLAN will occasionally show the wrong execution plan for a SQL statement.  SQL*Plus’ AUTOTRACE feature experiences a similar problem with generating accurate plans from time to time, especially when the SQL statement uses bind variables.  Did you know that TKPROF may also show the wrong execution plan from time to time?  I set up a simple test case to demonstrate this behavior.

The test case tables:

CREATE TABLE T1(
  C1 NUMBER,
  C2 VARCHAR2(255),
  PRIMARY KEY (C1));

CREATE TABLE T2(
  C1 NUMBER,
  C2 VARCHAR2(255),
  PRIMARY KEY (C1));

INSERT INTO
  T1
SELECT
  ROWNUM,
  LPAD('A',255,'A')
FROM
  (SELECT
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=10000) V1,
  (SELECT
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=30) V2;

INSERT INTO
  T2
SELECT
  ROWNUM,
  LPAD('A',255,'A')
FROM
  (SELECT
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=10000) V1,
  (SELECT
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=30) V2;

COMMIT;

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T1',CASCADE=>TRUE)
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T2',CASCADE=>TRUE)

The above creates two tables, each with 300,000 rows with a primary key column (thus an index exists for the column), and a second column that acts as padding to make the row longer (to discourage full table scans).

The test script that I built looks like this:

ALTER SESSION SET STATISTICS_LEVEL='ALL';

VARIABLE N1 NUMBER
VARIABLE N2 NUMBER

SET AUTOTRACE TRACEONLY EXPLAIN
SET LINESIZE 150
SET PAGESIZE 2000
SET ARRAYSIZE 100

SPOOL DIFF_EXPLAIN_PLAN_TEST.TXT

EXEC :N1:=1
EXEC :N2:=2

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=10

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=100

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=1000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=10000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=100000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=300000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

/* --------------------------------------------------- */

ALTER SYSTEM FLUSH SHARED_POOL;

SET AUTOTRACE OFF

EXEC :N1:=1
EXEC :N2:=2

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

EXEC :N1:=1
EXEC :N2:=10

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

EXEC :N1:=1
EXEC :N2:=100

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

EXEC :N1:=1
EXEC :N2:=1000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

EXEC :N1:=1
EXEC :N2:=10000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

EXEC :N1:=1
EXEC :N2:=100000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

EXEC :N1:=1
EXEC :N2:=300000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

/* ------------------------------ */

SET AUTOTRACE TRACEONLY STATISTICS
ALTER SESSION SET EVENTS '10046 TRACE NAME CONTEXT FOREVER, LEVEL 8';
ALTER SESSION SET TRACEFILE_IDENTIFIER = 'DIFF_EXPLAIN_PLAN_TEST';

EXEC :N1:=1
EXEC :N2:=2

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=10
SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=100

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=1000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=10000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=100000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

EXEC :N1:=1
EXEC :N2:=300000

SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2;

ALTER SESSION SET EVENTS '10046 TRACE NAME CONTEXT OFF';

SET AUTOTRACE OFF

SPOOL OFF 

There are three sections in the script:

  1. Display the AUTOTRACE generated execution plans without executing the queries, adjusting the bind variable values before each execution.
  2. Execute the SQL statement and display the actual execution plan using DBMS_XPLAN, adjusting the bind variable values before each execution (note that the flush of the shared pool seems to be required, the SQL statement is executed a couple times with each set of bind variable values to trigger adaptive cursor sharing in Oracle 11.1.0.6 and above).
  3. Execute the SQL statement displaying only the execution statistics, with a 10046 trace at level 8 enabled.

What were the results on Oracle 11.1.0.7 (you may see different results on 11.1.0.6 and 11.2.0.1)?  Section 1 of the output (slightly cleaned up) follows – keep on eye on the predicted number of rows and the Predicate Information section:

SQL> EXEC :N1:=1
SQL> EXEC :N2:=2
SQL>
SQL> SELECT
  2    T1.C1,
  3    T2.C1,
  4    SUBSTR(T1.C2,1,1) T1_C2,
  5    SUBSTR(T2.C2,1,1) T2_C2
  6  FROM
  7    T1,
  8    T2
  9  WHERE
 10    T1.C1=T2.C1
 11    AND T1.C1 BETWEEN :N1 AND :N2;

Execution Plan
----------------------------------------------------------                    
Plan hash value: 2267210268   

------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name         | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              | 15000 |  7617K|       |   498   (1)| 00:00:06 |
|*  1 |  FILTER                       |              |       |       |       |            |          |
|*  2 |   HASH JOIN                   |              | 15000 |  7617K|  3992K|   498   (1)| 00:00:06 |
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 |  1350 |       |       |     4   (0)| 00:00:01 |
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 |  1350 |       |       |     4   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(TO_NUMBER(:N1)<=TO_NUMBER(:N2))        
   2 - access("T1"."C1"="T2"."C1")                   
   4 - access("T1"."C1">=TO_NUMBER(:N1) AND "T1"."C1"<=TO_NUMBER(:N2))        
   6 - access("T2"."C1">=TO_NUMBER(:N1) AND "T2"."C1"<=TO_NUMBER(:N2))        

SQL>
SQL> EXEC :N1:=1
SQL> EXEC :N2:=10
SQL>
SQL> SELECT
  2    T1.C1,
  3    T2.C1,
  4    SUBSTR(T1.C2,1,1) T1_C2,
  5    SUBSTR(T2.C2,1,1) T2_C2
  6  FROM
  7    T1,
  8    T2
  9  WHERE
 10    T1.C1=T2.C1
 11    AND T1.C1 BETWEEN :N1 AND :N2;

Execution Plan
----------------------------------------------------------                    
Plan hash value: 2267210268   

------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name         | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              | 15000 |  7617K|       |   498   (1)| 00:00:06 |
|*  1 |  FILTER                       |              |       |       |       |            |          |
|*  2 |   HASH JOIN                   |              | 15000 |  7617K|  3992K|   498   (1)| 00:00:06 |
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 |  1350 |       |       |     4   (0)| 00:00:01 |
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 |  1350 |       |       |     4   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(TO_NUMBER(:N1)<=TO_NUMBER(:N2))        
   2 - access("T1"."C1"="T2"."C1")                   
   4 - access("T1"."C1">=TO_NUMBER(:N1) AND "T1"."C1"<=TO_NUMBER(:N2))        
   6 - access("T2"."C1">=TO_NUMBER(:N1) AND "T2"."C1"<=TO_NUMBER(:N2))        

SQL>
SQL> EXEC :N1:=1
SQL> EXEC :N2:=100
SQL>
SQL> SELECT
  2    T1.C1,
  3    T2.C1,
  4    SUBSTR(T1.C2,1,1) T1_C2,
  5    SUBSTR(T2.C2,1,1) T2_C2
  6  FROM
  7    T1,
  8    T2
  9  WHERE
 10    T1.C1=T2.C1
 11    AND T1.C1 BETWEEN :N1 AND :N2;

Execution Plan
----------------------------------------------------------                    
Plan hash value: 2267210268   

------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name         | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              | 15000 |  7617K|       |   498   (1)| 00:00:06 |
|*  1 |  FILTER                       |              |       |       |       |            |          |
|*  2 |   HASH JOIN                   |              | 15000 |  7617K|  3992K|   498   (1)| 00:00:06 |
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 |  1350 |       |       |     4   (0)| 00:00:01 |
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 |  1350 |       |       |     4   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(TO_NUMBER(:N1)<=TO_NUMBER(:N2))        
   2 - access("T1"."C1"="T2"."C1")                   
   4 - access("T1"."C1">=TO_NUMBER(:N1) AND "T1"."C1"<=TO_NUMBER(:N2))        
   6 - access("T2"."C1">=TO_NUMBER(:N1) AND "T2"."C1"<=TO_NUMBER(:N2))        

SQL>
SQL> EXEC :N1:=1
SQL> EXEC :N2:=1000
SQL>
SQL> SELECT
  2    T1.C1,
  3    T2.C1,
  4    SUBSTR(T1.C2,1,1) T1_C2,
  5    SUBSTR(T2.C2,1,1) T2_C2
  6  FROM
  7    T1,
  8    T2
  9  WHERE
 10    T1.C1=T2.C1
 11    AND T1.C1 BETWEEN :N1 AND :N2;

Execution Plan
----------------------------------------------------------                    
Plan hash value: 2267210268   

------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name         | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              | 15000 |  7617K|       |   498   (1)| 00:00:06 |
|*  1 |  FILTER                       |              |       |       |       |            |          |
|*  2 |   HASH JOIN                   |              | 15000 |  7617K|  3992K|   498   (1)| 00:00:06 |
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 |  1350 |       |       |     4   (0)| 00:00:01 |
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 |  1350 |       |       |     4   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(TO_NUMBER(:N1)<=TO_NUMBER(:N2))        
   2 - access("T1"."C1"="T2"."C1")                   
   4 - access("T1"."C1">=TO_NUMBER(:N1) AND "T1"."C1"<=TO_NUMBER(:N2))        
   6 - access("T2"."C1">=TO_NUMBER(:N1) AND "T2"."C1"<=TO_NUMBER(:N2))        

SQL>
SQL> EXEC :N1:=1
SQL> EXEC :N2:=10000
SQL>
SQL> SELECT
  2    T1.C1,
  3    T2.C1,
  4    SUBSTR(T1.C2,1,1) T1_C2,
  5    SUBSTR(T2.C2,1,1) T2_C2
  6  FROM
  7    T1,
  8    T2
  9  WHERE
 10    T1.C1=T2.C1
 11    AND T1.C1 BETWEEN :N1 AND :N2;

Execution Plan
----------------------------------------------------------                    
Plan hash value: 2267210268   

------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name         | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              | 15000 |  7617K|       |   498   (1)| 00:00:06 |
|*  1 |  FILTER                       |              |       |       |       |            |          |
|*  2 |   HASH JOIN                   |              | 15000 |  7617K|  3992K|   498   (1)| 00:00:06 |
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 |  1350 |       |       |     4   (0)| 00:00:01 |
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 |  1350 |       |       |     4   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(TO_NUMBER(:N1)<=TO_NUMBER(:N2))        
   2 - access("T1"."C1"="T2"."C1")                   
   4 - access("T1"."C1">=TO_NUMBER(:N1) AND "T1"."C1"<=TO_NUMBER(:N2))        
   6 - access("T2"."C1">=TO_NUMBER(:N1) AND "T2"."C1"<=TO_NUMBER(:N2))        

SQL>
SQL> EXEC :N1:=1
SQL> EXEC :N2:=100000
SQL>
SQL> SELECT
  2    T1.C1,
  3    T2.C1,
  4    SUBSTR(T1.C2,1,1) T1_C2,
  5    SUBSTR(T2.C2,1,1) T2_C2
  6  FROM
  7    T1,
  8    T2
  9  WHERE
 10    T1.C1=T2.C1
 11    AND T1.C1 BETWEEN :N1 AND :N2;

Execution Plan
----------------------------------------------------------                    
Plan hash value: 2267210268   

------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name         | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              | 15000 |  7617K|       |   498   (1)| 00:00:06 |
|*  1 |  FILTER                       |              |       |       |       |            |          |
|*  2 |   HASH JOIN                   |              | 15000 |  7617K|  3992K|   498   (1)| 00:00:06 |
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 |  1350 |       |       |     4   (0)| 00:00:01 |
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 |  1350 |       |       |     4   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(TO_NUMBER(:N1)<=TO_NUMBER(:N2))        
   2 - access("T1"."C1"="T2"."C1")                   
   4 - access("T1"."C1">=TO_NUMBER(:N1) AND "T1"."C1"<=TO_NUMBER(:N2))        
   6 - access("T2"."C1">=TO_NUMBER(:N1) AND "T2"."C1"<=TO_NUMBER(:N2))        

SQL>
SQL> EXEC :N1:=1
SQL> EXEC :N2:=300000
SQL>
SQL> SELECT
  2    T1.C1,
  3    T2.C1,
  4    SUBSTR(T1.C2,1,1) T1_C2,
  5    SUBSTR(T2.C2,1,1) T2_C2
  6  FROM
  7    T1,
  8    T2
  9  WHERE
 10    T1.C1=T2.C1
 11    AND T1.C1 BETWEEN :N1 AND :N2;

Execution Plan
----------------------------------------------------------                    
Plan hash value: 2267210268   

------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name         | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |              | 15000 |  7617K|       |   498   (1)| 00:00:06 |
|*  1 |  FILTER                       |              |       |       |       |            |          |
|*  2 |   HASH JOIN                   |              | 15000 |  7617K|  3992K|   498   (1)| 00:00:06 |
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 |  1350 |       |       |     4   (0)| 00:00:01 |
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           | 15000 |  3808K|       |    55   (0)| 00:00:01 |
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 |  1350 |       |       |     4   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(TO_NUMBER(:N1)<=TO_NUMBER(:N2))        
   2 - access("T1"."C1"="T2"."C1")                   
   4 - access("T1"."C1">=TO_NUMBER(:N1) AND "T1"."C1"<=TO_NUMBER(:N2))        
   6 - access("T2"."C1">=TO_NUMBER(:N1) AND "T2"."C1"<=TO_NUMBER(:N2))

Notice that the optimizer is estimating that each table will return 15,000 of the 300,000 rows, 5% of the rows in the tables.  Also notice in the Predicate Information section that AUTOTRACE is treating each of the bind variables as if it were defined as a VARCHAR2, rather than a NUMBER.

Next, we will move on to the second section of the script to see the actual execution plans.  Keep an eye on the E-Rows column, the execution plan, and the order of the query results.

SQL> EXEC :N1:=1
SQL> EXEC :N2:=2
SQL> SELECT
  2    T1.C1,
  3    T2.C1,
  4    SUBSTR(T1.C2,1,1) T1_C2,
  5    SUBSTR(T2.C2,1,1) T2_C2
  6  FROM
  7    T1,
  8    T2
  9  WHERE
 10    T1.C1=T2.C1
 11    AND T1.C1 BETWEEN :N1 AND :N2;

        C1         C1 T T     
---------- ---------- - -     
         1          1 A A     
         2          2 A A     

SQL_ID  bgjafhqwjt1zt, child number 0
Plan hash value: 4256353061   
---------------------------------------------------------------------------------------------------------                     
| Id  | Operation                      | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                     
---------------------------------------------------------------------------------------------------------                     
|   0 | SELECT STATEMENT               |              |      1 |        |      2 |00:00:00.01 |      11 |                     
|*  1 |  FILTER                        |              |      1 |        |      2 |00:00:00.01 |      11 |                     
|   2 |   NESTED LOOPS                 |              |      1 |        |      2 |00:00:00.01 |      11 |                     
|   3 |    NESTED LOOPS                |              |      1 |      1 |      2 |00:00:00.01 |       9 |                     
|   4 |     TABLE ACCESS BY INDEX ROWID| T1           |      1 |      2 |      2 |00:00:00.01 |       5 |                     
|*  5 |      INDEX RANGE SCAN          | SYS_C0030339 |      1 |      2 |      2 |00:00:00.01 |       3 |                     
|*  6 |     INDEX UNIQUE SCAN          | SYS_C0030340 |      2 |      1 |      2 |00:00:00.01 |       4 |                     
|   7 |    TABLE ACCESS BY INDEX ROWID | T2           |      2 |      1 |      2 |00:00:00.01 |       2 |                     
---------------------------------------------------------------------------------------------------------                     

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(:N1<=:N2)
   5 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)     
   6 - access("T1"."C1"="T2"."C1")                   
       filter(("T2"."C1"<=:N2 AND "T2"."C1">=:N1))   

SQL> EXEC :N1:=1
SQL> EXEC :N2:=10
SQL> SELECT
  2    T1.C1,
  3    T2.C1,
  4    SUBSTR(T1.C2,1,1) T1_C2,
  5    SUBSTR(T2.C2,1,1) T2_C2
  6  FROM
  7    T1,
  8    T2
  9  WHERE
 10    T1.C1=T2.C1
 11    AND T1.C1 BETWEEN :N1 AND :N2;

        C1         C1 T T     
---------- ---------- - -     
         1          1 A A     
         2          2 A A     
         3          3 A A     
         4          4 A A     
         5          5 A A     
         6          6 A A     
         7          7 A A     
         8          8 A A     
         9          9 A A     
        10         10 A A     

SQL_ID  bgjafhqwjt1zt, child number 0
Plan hash value: 4256353061   
---------------------------------------------------------------------------------------------------------                     
| Id  | Operation                      | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                     
---------------------------------------------------------------------------------------------------------                     
|   0 | SELECT STATEMENT               |              |      1 |        |     10 |00:00:00.01 |      24 |                     
|*  1 |  FILTER                        |              |      1 |        |     10 |00:00:00.01 |      24 |                     
|   2 |   NESTED LOOPS                 |              |      1 |        |     10 |00:00:00.01 |      24 |                     
|   3 |    NESTED LOOPS                |              |      1 |      1 |     10 |00:00:00.01 |      14 |                     
|   4 |     TABLE ACCESS BY INDEX ROWID| T1           |      1 |      2 |     10 |00:00:00.01 |       5 |                     
|*  5 |      INDEX RANGE SCAN          | SYS_C0030339 |      1 |      2 |     10 |00:00:00.01 |       3 |                     
|*  6 |     INDEX UNIQUE SCAN          | SYS_C0030340 |     10 |      1 |     10 |00:00:00.01 |       9 |                     
|   7 |    TABLE ACCESS BY INDEX ROWID | T2           |     10 |      1 |     10 |00:00:00.01 |      10 |                     
---------------------------------------------------------------------------------------------------------                     

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(:N1<=:N2)
   5 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)     
   6 - access("T1"."C1"="T2"."C1")                   
       filter(("T2"."C1"<=:N2 AND "T2"."C1">=:N1))    

SQL_ID  bgjafhqwjt1zt, child number 0                
Plan hash value: 4256353061   
---------------------------------------------------------------------------------------------------------                     
| Id  | Operation                      | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                     
---------------------------------------------------------------------------------------------------------                     
|   0 | SELECT STATEMENT               |              |      1 |        |    100 |00:00:00.01 |     117 |                     
|*  1 |  FILTER                        |              |      1 |        |    100 |00:00:00.01 |     117 |                     
|   2 |   NESTED LOOPS                 |              |      1 |        |    100 |00:00:00.01 |     117 |                     
|   3 |    NESTED LOOPS                |              |      1 |      1 |    100 |00:00:00.01 |      17 |                     
|   4 |     TABLE ACCESS BY INDEX ROWID| T1           |      1 |      2 |    100 |00:00:00.01 |       8 |                     
|*  5 |      INDEX RANGE SCAN          | SYS_C0030339 |      1 |      2 |    100 |00:00:00.01 |       3 |                     
|*  6 |     INDEX UNIQUE SCAN          | SYS_C0030340 |    100 |      1 |    100 |00:00:00.01 |       9 |                     
|   7 |    TABLE ACCESS BY INDEX ROWID | T2           |    100 |      1 |    100 |00:00:00.01 |     100 |                     
---------------------------------------------------------------------------------------------------------                     

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(:N1<=:N2)
   5 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)     
   6 - access("T1"."C1"="T2"."C1")                   
       filter(("T2"."C1"<=:N2 AND "T2"."C1">=:N1))    

SQL> EXEC :N1:=1
SQL> EXEC :N2:=1000

        C1         C1 T T     
---------- ---------- - -     
         1          1 A A     
         2          2 A A     
         3          3 A A     
         4          4 A A     
         5          5 A A     
...
       997        997 A A     
       998        998 A A     
       999        999 A A     
      1000       1000 A A     

SQL_ID  bgjafhqwjt1zt, child number 0                
Plan hash value: 4256353061   
---------------------------------------------------------------------------------------------------------                     
| Id  | Operation                      | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                     
---------------------------------------------------------------------------------------------------------                     
|   0 | SELECT STATEMENT               |              |      1 |        |   1000 |00:00:00.01 |    1087 |                     
|*  1 |  FILTER                        |              |      1 |        |   1000 |00:00:00.01 |    1087 |                     
|   2 |   NESTED LOOPS                 |              |      1 |        |   1000 |00:00:00.01 |    1087 |                     
|   3 |    NESTED LOOPS                |              |      1 |      1 |   1000 |00:00:00.01 |      87 |                     
|   4 |     TABLE ACCESS BY INDEX ROWID| T1           |      1 |      2 |   1000 |00:00:00.01 |      61 |                     
|*  5 |      INDEX RANGE SCAN          | SYS_C0030339 |      1 |      2 |   1000 |00:00:00.01 |      13 |                     
|*  6 |     INDEX UNIQUE SCAN          | SYS_C0030340 |   1000 |      1 |   1000 |00:00:00.01 |      26 |                     
|   7 |    TABLE ACCESS BY INDEX ROWID | T2           |   1000 |      1 |   1000 |00:00:00.01 |    1000 |                     
---------------------------------------------------------------------------------------------------------                     

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(:N1<=:N2)
   5 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)     
   6 - access("T1"."C1"="T2"."C1")                   
       filter(("T2"."C1"<=:N2 AND "T2"."C1">=:N1))   

SQL> EXEC :N1:=1
SQL> EXEC :N2:=10000

        C1         C1 T T     
---------- ---------- - -     
         1          1 A A     
         2          2 A A     
         3          3 A A     
         4          4 A A     
         5          5 A A     
...
      9997       9997 A A     
      9998       9998 A A     
      9999       9999 A A     
     10000      10000 A A     

SQL_ID  bgjafhqwjt1zt, child number 1                
Plan hash value: 2267210268   
-----------------------------------------------------------------------------------------------------------------------------------                  
| Id  | Operation                     | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |                  
-----------------------------------------------------------------------------------------------------------------------------------                  
|   0 | SELECT STATEMENT              |              |      1 |        |  10000 |00:00:00.03 |     975 |       |       |          |                  
|*  1 |  FILTER                       |              |      1 |        |  10000 |00:00:00.03 |     975 |       |       |          |                  
|*  2 |   HASH JOIN                   |              |      1 |   9999 |  10000 |00:00:00.03 |     975 |  3337K|   921K| 3324K (0)|                  
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           |      1 |  10000 |  10000 |00:00:00.01 |     390 |       |       |          |                  
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 |      1 |  10000 |  10000 |00:00:00.01 |      19 |       |       |          |                  
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           |      1 |  10000 |  10000 |00:00:00.01 |     585 |       |       |          |                  
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 |      1 |  10000 |  10000 |00:00:00.01 |     118 |       |       |          |                  
-----------------------------------------------------------------------------------------------------------------------------------                  

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(:N1<=:N2)
   2 - access("T1"."C1"="T2"."C1")                   
   4 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)     
   6 - access("T2"."C1">=:N1 AND "T2"."C1"<=:N2)     

SQL> EXEC :N1:=1
SQL> EXEC :N2:=100000

        C1         C1 T T     
---------- ---------- - -     
        28         28 A A     
        29         29 A A     
        30         30 A A     
        31         31 A A     
        32         32 A A     
...
     98763      98763 A A     
     98764      98764 A A     
     98765      98765 A A     
     98766      98766 A A     

SQL_ID  bgjafhqwjt1zt, child number 2                
Plan hash value: 487071653    
-----------------------------------------------------------------------------------------------------------------             
| Id  | Operation           | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |             
-----------------------------------------------------------------------------------------------------------------             
|   0 | SELECT STATEMENT    |      |      1 |        |    100K|00:00:00.29 |   23219 |       |       |          |             
|*  1 |  FILTER             |      |      1 |        |    100K|00:00:00.29 |   23219 |       |       |          |             
|*  2 |   HASH JOIN         |      |      1 |    100K|    100K|00:00:00.29 |   23219 |    28M|  3683K|   29M (0)|             
|*  3 |    TABLE ACCESS FULL| T1   |      1 |    100K|    100K|00:00:00.03 |   11128 |       |       |          |             
|*  4 |    TABLE ACCESS FULL| T2   |      1 |    100K|    100K|00:00:00.03 |   12091 |       |       |          |             
-----------------------------------------------------------------------------------------------------------------             

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(:N1<=:N2)
   2 - access("T1"."C1"="T2"."C1")                   
   3 - filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))   
   4 - filter(("T2"."C1"<=:N2 AND "T2"."C1">=:N1))   

SQL> EXEC :N1:=1
SQL> EXEC :N2:=300000

        C1         C1 T T     
---------- ---------- - -     
        31         31 A A     
        34         34 A A     
        35         35 A A     
        37         37 A A     
        44         44 A A     
...
    276467     276467 A A     
    276910     276910 A A     
    277750     277750 A A     
    277771     277771 A A     

SQL_ID  bgjafhqwjt1zt, child number 3                
Plan hash value: 487071653    
---------------------------------------------------------------------------------------------------------------------------------------------        
| Id  | Operation           | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem | Used-Tmp|        
---------------------------------------------------------------------------------------------------------------------------------------------        
|   0 | SELECT STATEMENT    |      |      1 |        |    300K|00:00:06.67 |   23351 |  15996 |  15996 |       |       |          |         |        
|*  1 |  FILTER             |      |      1 |        |    300K|00:00:06.67 |   23351 |  15996 |  15996 |       |       |          |         |        
|*  2 |   HASH JOIN         |      |      1 |    300K|    300K|00:00:06.37 |   23351 |  15996 |  15996 |    85M|  7366K|   43M (1)|     132K|        
|*  3 |    TABLE ACCESS FULL| T1   |      1 |    300K|    300K|00:00:00.01 |   11128 |      0 |      0 |       |       |          |         |        
|*  4 |    TABLE ACCESS FULL| T2   |      1 |    300K|    300K|00:00:00.01 |   12223 |      0 |      0 |       |       |          |         |        
---------------------------------------------------------------------------------------------------------------------------------------------        

Predicate Information (identified by operation id):  
---------------------------------------------------  
   1 - filter(:N1<=:N2)
   2 - access("T1"."C1"="T2"."C1")                   
   3 - filter(("T1"."C1">=:N1 AND "T1"."C1"<=:N2))   
   4 - filter(("T2"."C1">=:N1 AND "T2"."C1"<=:N2))   

Next, we will move on to the third section of the script to see the output of TKPROF.  The Oracle 11.1.0.6 client home happened to be first in my path before any other Oracle home.  I renamed the trace file from the server and processed it with TKPROF:

tkprof DIFF_EXPLAIN_PLAN_TEST.trc DIFF_EXPLAIN_PLAN_TEST_TRC.txt

What was in the DIFF_EXPLAIN_PLAN_TEST_TRC.txt file generated by TKPROF?

********************************************************************************
SELECT
  T1.C1,
  T2.C1,
  SUBSTR(T1.C2,1,1) T1_C2,
  SUBSTR(T2.C2,1,1) T2_C2
FROM
  T1,
  T2
WHERE
  T1.C1=T2.C1
  AND T1.C1 BETWEEN :N1 AND :N2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        7      0.00       0.00          0          0          0           0
Execute      7      0.01       0.03          0          0          0           0
Fetch     4120      3.52      10.91      18216      47844          0      411112
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     4134      3.54      10.95      18216      47844          0      411112

Misses in library cache during parse: 0
Misses in library cache during execute: 4
Optimizer mode: ALL_ROWS
Parsing user id: 518 

Rows     Row Source Operation
-------  ---------------------------------------------------
      2  FILTER  (cr=11 pr=0 pw=0 time=0 us)
      2   NESTED LOOPS  (cr=11 pr=0 pw=0 time=0 us)
      2    NESTED LOOPS  (cr=9 pr=0 pw=0 time=0 us cost=5 size=520 card=1)
      2     TABLE ACCESS BY INDEX ROWID T1 (cr=5 pr=0 pw=0 time=0 us cost=3 size=520 card=2)
      2      INDEX RANGE SCAN SYS_C0030337 (cr=3 pr=0 pw=0 time=0 us cost=2 size=0 card=2)(object id 87187)
      2     INDEX UNIQUE SCAN SYS_C0030338 (cr=4 pr=0 pw=0 time=0 us cost=0 size=0 card=1)(object id 87189)
      2    TABLE ACCESS BY INDEX ROWID T2 (cr=2 pr=0 pw=0 time=0 us cost=1 size=260 card=1)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                    4120        0.00          0.02
  SQL*Net message from client                  4120        0.01          3.21
  direct path write temp                       1128        0.99          2.06
  direct path read temp                        1128        0.17          5.10
********************************************************************************

Interesting, all seven executions with different bind variable values used the same execution plan.  That means that this command lied?:

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

Wait a minute, that is not in the title of this article.  If only I knew how to read a raw 10046 extended SQL trace, I could read this:

STAT #8 id=1 cnt=2 pid=0 pos=1 obj=0 op='FILTER  (cr=11 pr=0 pw=0 time=0 us)'
STAT #8 id=2 cnt=2 pid=1 pos=1 obj=0 op='NESTED LOOPS  (cr=11 pr=0 pw=0 time=0 us)'
STAT #8 id=3 cnt=2 pid=2 pos=1 obj=0 op='NESTED LOOPS  (cr=9 pr=0 pw=0 time=0 us cost=5 size=520 card=1)'
STAT #8 id=4 cnt=2 pid=3 pos=1 obj=87264 op='TABLE ACCESS BY INDEX ROWID T1 (cr=5 pr=0 pw=0 time=0 us cost=3 size=520 card=2)'
STAT #8 id=5 cnt=2 pid=4 pos=1 obj=87265 op='INDEX RANGE SCAN SYS_C0030339 (cr=3 pr=0 pw=0 time=0 us cost=2 size=0 card=2)'
STAT #8 id=6 cnt=2 pid=3 pos=2 obj=87267 op='INDEX UNIQUE SCAN SYS_C0030340 (cr=4 pr=0 pw=0 time=0 us cost=0 size=0 card=1)'
STAT #8 id=7 cnt=2 pid=2 pos=2 obj=87266 op='TABLE ACCESS BY INDEX ROWID T2 (cr=2 pr=0 pw=0 time=0 us cost=1 size=260 card=1)'
...
STAT #5 id=1 cnt=300000 pid=0 pos=1 obj=0 op='FILTER  (cr=23515 pr=15705 pw=15705 time=4741368 us)'
STAT #5 id=2 cnt=300000 pid=1 pos=1 obj=0 op='HASH JOIN  (cr=23515 pr=15705 pw=15705 time=4741368 us cost=13774 size=156000000 card=300000)'
STAT #5 id=3 cnt=300000 pid=2 pos=1 obj=87264 op='TABLE ACCESS FULL T1 (cr=11128 pr=0 pw=0 time=31126 us cost=3024 size=78000000 card=300000)'
STAT #5 id=4 cnt=300000 pid=2 pos=2 obj=87266 op='TABLE ACCESS FULL T2 (cr=12387 pr=0 pw=0 time=155851 us cost=3024 size=78000000 card=300000)'

If you do know how to read the STAT lines in a 10046 extended SQL trace, you would know that the execution plan definitely did change between the first and last execution when 10046 extended SQL tracing was enabled.

We might be inclined to do some checking, like this:

SELECT
  CHILD_NUMBER,
  EXECUTIONS,
  ROWS_PROCESSED,
  BUFFER_GETS,
  BIND_SET_HASH_VALUE
FROM
  V$SQL_CS_STATISTICS
WHERE
  SQL_ID='bgjafhqwjt1zt'
ORDER BY
  CHILD_NUMBER;

CHILD_NUMBER EXECUTIONS ROWS_PROCESSED BUFFER_GETS BIND_SET_HASH_VALUE
------------ ---------- -------------- ----------- -------------------
           0          1          12001        1087           722894381
           0          1             25         124          2702357211
           1          1         180000         975           982654583
           2          1        1000000       23219          2772294946
           3          1        3000000       23351          1545127490
           4          1             25          11          2702357211
           5          1            140           8             6258482
           6          1           1400          14          4281096765
           7          1          18000         102           722894381

Or, we might try doing something like this:

SPOOL 'DIFF_EXPLAIN_PLANS_ADAPT.TXT'

SELECT
  *
FROM
  TABLE(DBMS_XPLAN.DISPLAY_CURSOR('bgjafhqwjt1zt',NULL,'TYPICAL'));

SPOOL OFF

PLAN_TABLE_OUTPUT      
------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  bgjafhqwjt1zt, child number 0                    
-------------------------------------                    
SELECT   T1.C1,   T2.C1,   SUBSTR(T1.C2,1,1) T1_C2,   SUBSTR(T2.C2,1,1)         
T2_C2 FROM   T1,   T2 WHERE   T1.C1=T2.C1   AND T1.C1 BETWEEN :N1 AND           
:N2                    

Plan hash value: 4256353061       
-----------------------------------------------------------------------------------------------        
| Id  | Operation                      | Name         | Rows  | Bytes | Cost (%CPU)| Time     |        
-----------------------------------------------------------------------------------------------        
|   0 | SELECT STATEMENT               |              |       |       |     5 (100)|          |        
|*  1 |  FILTER                        |              |       |       |            |          |        
|   2 |   NESTED LOOPS                 |              |       |       |            |          |        
|   3 |    NESTED LOOPS                |              |     1 |   520 |     5   (0)| 00:00:01 |        
|   4 |     TABLE ACCESS BY INDEX ROWID| T1           |     2 |   520 |     3   (0)| 00:00:01 |        
|*  5 |      INDEX RANGE SCAN          | SYS_C0030339 |     2 |       |     2   (0)| 00:00:01 |        
|*  6 |     INDEX UNIQUE SCAN          | SYS_C0030340 |     1 |       |     0   (0)|          |        
|   7 |    TABLE ACCESS BY INDEX ROWID | T2           |     1 |   260 |     1   (0)| 00:00:01 |        
-----------------------------------------------------------------------------------------------        

Predicate Information (identified by operation id):      
---------------------------------------------------      
   1 - filter(:N1<=:N2)
   5 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)         
   6 - access("T1"."C1"="T2"."C1")                       
       filter(("T2"."C1"<=:N2 AND "T2"."C1">=:N1))       

SQL_ID  bgjafhqwjt1zt, child number 1                    
-------------------------------------                    
SELECT   T1.C1,   T2.C1,   SUBSTR(T1.C2,1,1) T1_C2,   SUBSTR(T2.C2,1,1)         
T2_C2 FROM   T1,   T2 WHERE   T1.C1=T2.C1   AND T1.C1 BETWEEN :N1 AND           
:N2                    

Plan hash value: 2267210268       
------------------------------------------------------------------------------------------------------ 
| Id  | Operation                     | Name         | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | 
------------------------------------------------------------------------------------------------------ 
|   0 | SELECT STATEMENT              |              |       |       |       |  1042 (100)|          | 
|*  1 |  FILTER                       |              |       |       |       |            |          | 
|*  2 |   HASH JOIN                   |              |  9999 |  5077K|  2664K|  1042   (1)| 00:00:13 | 
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           | 10000 |  2539K|       |   391   (0)| 00:00:05 | 
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 | 10000 |       |       |    20   (0)| 00:00:01 | 
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           | 10000 |  2539K|       |   391   (0)| 00:00:05 | 
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 | 10000 |       |       |    20   (0)| 00:00:01 | 
------------------------------------------------------------------------------------------------------ 

Predicate Information (identified by operation id):      
---------------------------------------------------      
   1 - filter(:N1<=:N2)
   2 - access("T1"."C1"="T2"."C1")                       
   4 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)         
   6 - access("T2"."C1">=:N1 AND "T2"."C1"<=:N2)         

SQL_ID  bgjafhqwjt1zt, child number 2                    
-------------------------------------                    
SELECT   T1.C1,   T2.C1,   SUBSTR(T1.C2,1,1) T1_C2,   SUBSTR(T2.C2,1,1)         
T2_C2 FROM   T1,   T2 WHERE   T1.C1=T2.C1   AND T1.C1 BETWEEN :N1 AND           
:N2                    

Plan hash value: 487071653        
------------------------------------------------------------------------------------                   
| Id  | Operation           | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |                   
------------------------------------------------------------------------------------                   
|   0 | SELECT STATEMENT    |      |       |       |       |  8623 (100)|          |                   
|*  1 |  FILTER             |      |       |       |       |            |          |                   
|*  2 |   HASH JOIN         |      |   100K|    49M|    25M|  8623   (1)| 00:01:44 |                   
|*  3 |    TABLE ACCESS FULL| T1   |   100K|    24M|       |  3023   (1)| 00:00:37 |                   
|*  4 |    TABLE ACCESS FULL| T2   |   100K|    24M|       |  3023   (1)| 00:00:37 |                   
------------------------------------------------------------------------------------                   

Predicate Information (identified by operation id):      
---------------------------------------------------      
   1 - filter(:N1<=:N2)
   2 - access("T1"."C1"="T2"."C1")                       
   3 - filter(("T1"."C1"<=:N2 AND "T1"."C1">=:N1))       
   4 - filter(("T2"."C1"<=:N2 AND "T2"."C1">=:N1))       

SQL_ID  bgjafhqwjt1zt, child number 3                    
-------------------------------------                    
SELECT   T1.C1,   T2.C1,   SUBSTR(T1.C2,1,1) T1_C2,   SUBSTR(T2.C2,1,1)         
T2_C2 FROM   T1,   T2 WHERE   T1.C1=T2.C1   AND T1.C1 BETWEEN :N1 AND           
:N2                    

Plan hash value: 487071653        
------------------------------------------------------------------------------------                   
| Id  | Operation           | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |                   
------------------------------------------------------------------------------------                   
|   0 | SELECT STATEMENT    |      |       |       |       | 13774 (100)|          |                   
|*  1 |  FILTER             |      |       |       |       |            |          |                   
|*  2 |   HASH JOIN         |      |   300K|   148M|    77M| 13774   (1)| 00:02:46 |                   
|*  3 |    TABLE ACCESS FULL| T1   |   300K|    74M|       |  3024   (1)| 00:00:37 |                   
|*  4 |    TABLE ACCESS FULL| T2   |   300K|    74M|       |  3024   (1)| 00:00:37 |                   
------------------------------------------------------------------------------------                   

Predicate Information (identified by operation id):      
---------------------------------------------------      
   1 - filter(:N1<=:N2)
   2 - access("T1"."C1"="T2"."C1")                       
   3 - filter(("T1"."C1">=:N1 AND "T1"."C1"<=:N2))       
   4 - filter(("T2"."C1">=:N1 AND "T2"."C1"<=:N2))       

SQL_ID  bgjafhqwjt1zt, child number 4                    
-------------------------------------                    
SELECT   T1.C1,   T2.C1,   SUBSTR(T1.C2,1,1) T1_C2,   SUBSTR(T2.C2,1,1)         
T2_C2 FROM   T1,   T2 WHERE   T1.C1=T2.C1   AND T1.C1 BETWEEN :N1 AND           
:N2                    

Plan hash value: 4256353061       
-----------------------------------------------------------------------------------------------        
| Id  | Operation                      | Name         | Rows  | Bytes | Cost (%CPU)| Time     |        
-----------------------------------------------------------------------------------------------        
|   0 | SELECT STATEMENT               |              |       |       |     5 (100)|          |        
|*  1 |  FILTER                        |              |       |       |            |          |        
|   2 |   NESTED LOOPS                 |              |       |       |            |          |        
|   3 |    NESTED LOOPS                |              |     1 |   520 |     5   (0)| 00:00:01 |        
|   4 |     TABLE ACCESS BY INDEX ROWID| T1           |     2 |   520 |     3   (0)| 00:00:01 |        
|*  5 |      INDEX RANGE SCAN          | SYS_C0030339 |     2 |       |     2   (0)| 00:00:01 |        
|*  6 |     INDEX UNIQUE SCAN          | SYS_C0030340 |     1 |       |     0   (0)|          |        
|   7 |    TABLE ACCESS BY INDEX ROWID | T2           |     1 |   260 |     1   (0)| 00:00:01 |        
-----------------------------------------------------------------------------------------------        

Predicate Information (identified by operation id):      
---------------------------------------------------      
   1 - filter(:N1<=:N2)
   5 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)         
   6 - access("T1"."C1"="T2"."C1")                       
       filter(("T2"."C1"<=:N2 AND "T2"."C1">=:N1))       

SQL_ID  bgjafhqwjt1zt, child number 5                    
-------------------------------------                    
SELECT   T1.C1,   T2.C1,   SUBSTR(T1.C2,1,1) T1_C2,   SUBSTR(T2.C2,1,1)         
T2_C2 FROM   T1,   T2 WHERE   T1.C1=T2.C1   AND T1.C1 BETWEEN :N1 AND           
:N2                    

Plan hash value: 2267210268       
----------------------------------------------------------------------------------------------         
| Id  | Operation                     | Name         | Rows  | Bytes | Cost (%CPU)| Time     |         
----------------------------------------------------------------------------------------------         
|   0 | SELECT STATEMENT              |              |       |       |     7 (100)|          |         
|*  1 |  FILTER                       |              |       |       |            |          |         
|*  2 |   HASH JOIN                   |              |     9 |  4680 |     7  (15)| 00:00:01 |         
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           |    10 |  2600 |     3   (0)| 00:00:01 |         
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 |    10 |       |     2   (0)| 00:00:01 |         
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           |    10 |  2600 |     3   (0)| 00:00:01 |         
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 |    10 |       |     2   (0)| 00:00:01 |         
----------------------------------------------------------------------------------------------         

Predicate Information (identified by operation id):      
---------------------------------------------------      
   1 - filter(:N1<=:N2)
   2 - access("T1"."C1"="T2"."C1")                       
   4 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)         
   6 - access("T2"."C1">=:N1 AND "T2"."C1"<=:N2)         

SQL_ID  bgjafhqwjt1zt, child number 6                    
-------------------------------------                    
SELECT   T1.C1,   T2.C1,   SUBSTR(T1.C2,1,1) T1_C2,   SUBSTR(T2.C2,1,1)         
T2_C2 FROM   T1,   T2 WHERE   T1.C1=T2.C1   AND T1.C1 BETWEEN :N1 AND           
:N2                    

Plan hash value: 2267210268       
----------------------------------------------------------------------------------------------         
| Id  | Operation                     | Name         | Rows  | Bytes | Cost (%CPU)| Time     |         
----------------------------------------------------------------------------------------------         
|   0 | SELECT STATEMENT              |              |       |       |    13 (100)|          |         
|*  1 |  FILTER                       |              |       |       |            |          |         
|*  2 |   HASH JOIN                   |              |    99 | 51480 |    13   (8)| 00:00:01 |         
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           |   100 | 26000 |     6   (0)| 00:00:01 |         
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 |   100 |       |     2   (0)| 00:00:01 |         
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           |   100 | 26000 |     6   (0)| 00:00:01 |         
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 |   100 |       |     2   (0)| 00:00:01 |         
----------------------------------------------------------------------------------------------         

Predicate Information (identified by operation id):      
---------------------------------------------------      
   1 - filter(:N1<=:N2)
   2 - access("T1"."C1"="T2"."C1")                       
   4 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)         
   6 - access("T2"."C1">=:N1 AND "T2"."C1"<=:N2)         

SQL_ID  bgjafhqwjt1zt, child number 7                    
-------------------------------------                    
SELECT   T1.C1,   T2.C1,   SUBSTR(T1.C2,1,1) T1_C2,   SUBSTR(T2.C2,1,1)         
T2_C2 FROM   T1,   T2 WHERE   T1.C1=T2.C1   AND T1.C1 BETWEEN :N1 AND           
:N2                    

Plan hash value: 2267210268       
----------------------------------------------------------------------------------------------         
| Id  | Operation                     | Name         | Rows  | Bytes | Cost (%CPU)| Time     |         
----------------------------------------------------------------------------------------------         
|   0 | SELECT STATEMENT              |              |       |       |    83 (100)|          |         
|*  1 |  FILTER                       |              |       |       |            |          |         
|*  2 |   HASH JOIN                   |              |   999 |   507K|    83   (2)| 00:00:01 |         
|   3 |    TABLE ACCESS BY INDEX ROWID| T1           |  1000 |   253K|    41   (0)| 00:00:01 |         
|*  4 |     INDEX RANGE SCAN          | SYS_C0030339 |  1000 |       |     3   (0)| 00:00:01 |         
|   5 |    TABLE ACCESS BY INDEX ROWID| T2           |  1000 |   253K|    41   (0)| 00:00:01 |         
|*  6 |     INDEX RANGE SCAN          | SYS_C0030340 |  1000 |       |     3   (0)| 00:00:01 |         
----------------------------------------------------------------------------------------------         

Predicate Information (identified by operation id):      
---------------------------------------------------      
   1 - filter(:N1<=:N2)
   2 - access("T1"."C1"="T2"."C1")                       
   4 - access("T1"."C1">=:N1 AND "T1"."C1"<=:N2)         
   6 - access("T2"."C1">=:N1 AND "T2"."C1"<=:N2)         

You might be wondering if TKPROF in 11.1.0.7 or 11.2.0.1 lie about the execution plan captured in an extended 10046 SQL trace.  I would tell you, but this blog article is now too long. 🙂





The Effects of Potential NULL Values in Row Sources During Updates Using an IN Subquery

10 01 2010

January 10, 2010

A couple of years ago the following question appeared in the comp.databases.oracle.server Usenet group:
http://groups.google.com/group/comp.databases.oracle.server/browse_thread/thread/f3c3dea939bf9c12

I’m doing the following update (in Oracle Database 9.2.0.5):

UPDATE table1 t1
SET field1 = NULL
WHERE field2 NOT IN (SELECT /*+ HASH_AJ */ t2.field2 FROM table2 t2)

Instead of doing the ANTIJOIN, The database is performing a FILTER on
table1 by reading table2. Why doesn’t the hint work? For several
tables other that table1 the hint does work.

I did not mention it at the time, but I am not sure if that hint is valid inside the subquery – I think that it needs to appear immediately after the UPDATE keyword.  Taking a guess at the original poster’s problem, I set up a simple test case using Oracle 10.2.0.2:

CREATE TABLE T1 (FIELD1 NUMBER(12), FIELD2 NUMBER(12) NOT NULL);
CREATE TABLE T2 (FIELD1 NUMBER(12), FIELD2 NUMBER(12) NOT NULL);

INSERT INTO
  T1
SELECT
  100,
  ROWNUM*3
FROM
  DUAL
CONNECT BY
  LEVEL<=100000;

INSERT INTO
  T2
SELECT
  100,
  ROWNUM*9
FROM
  DUAL
CONNECT BY
  LEVEL<=100000;

COMMIT;
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T1');
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T2');

I enabled a 10053 trace and then executed the following SQL statement:

UPDATE t1
SET field1 = NULL
WHERE field2 NOT IN (SELECT /*+ HASH_AJ */ t2.field2 FROM t2)

The 10053 trace file showed the following plan:

============
Plan Table
============
-----------------------------------------+-----------------------------------+
| Id  | Operation              | Name    | Rows  | Bytes | Cost  | Time      |
-----------------------------------------+-----------------------------------+
| 0   | UPDATE STATEMENT       |         |       |       |   307 |           |
| 1   |  UPDATE                | T1      |       |       |       |           |
| 2   |   HASH JOIN RIGHT ANTI |         |   208 |  2496 |   307 |  00:00:04 |
| 3   |    TABLE ACCESS FULL   | T2      |   98K |  488K |    44 |  00:00:01 |
| 4   |    TABLE ACCESS FULL   | T1      |   98K |  684K |    86 |  00:00:02 |
-----------------------------------------+-----------------------------------+

That looks like the plan that the OP would like to see.  Now, repeat the test with the following table definitions:

CREATE TABLE T1 (FIELD1 NUMBER(12), FIELD2 NUMBER(12));
CREATE TABLE T2 (FIELD1 NUMBER(12), FIELD2 NUMBER(12));

The 10053 trace file showed the following execution plan when executing the same SQL statement:

============
Plan Table
============
---------------------------------------+-----------------------------------+
| Id  | Operation            | Name    | Rows  | Bytes | Cost  | Time      |
---------------------------------------+-----------------------------------+
| 0   | UPDATE STATEMENT     |         |       |       | 3886K |           |
| 1   |  UPDATE              | T1      |       |       |       |           |
| 2   |   FILTER             |         |       |       |       |           |
| 3   |    TABLE ACCESS FULL | T1      |   98K |  684K |    44 |  00:00:01 |
| 4   |    TABLE ACCESS FULL | T2      |     1 |     5 |    45 |  00:00:01 |
---------------------------------------+-----------------------------------+

My last comment in the post suggested that the problem might be that even though there might not be any NULL values in the columns, the column definitions might permit NULL values, and that alone might restrict the options that are available to the optimizer for re-writing the SQL statement into a more efficient form.

Let’s try another test case.  Let’s create two tables and see how a similar update statement performs with a larger dataset, for instance two tables with 10,000,000 rows each.  The table creation script follows:

CREATE TABLE T1 (COL1 NUMBER(12), COL2 NUMBER(12));
CREATE TABLE T2 (COL1 NUMBER(12), COL2 NUMBER(12));

INSERT INTO
  T1
SELECT
  100,
  ROWNUM*3
FROM
  (SELECT
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=10000) V1,
  (SELECT
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=1000) V2;

INSERT INTO
  T2
SELECT
  100,
  ROWNUM*9
FROM
  (SELECT
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=10000) V1,
  (SELECT
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=1000) V2;

COMMIT;

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T1',ESTIMATE_PERCENT=>NULL)
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T2',ESTIMATE_PERCENT=>NULL)

The test begins on Oracle 11.1.0.7 and later shifts to Oracle 10.2.0.4.  In the test script, we enable a 10053 trace, execute the UPDATE statement, display the execution plan with runtime statistics, move the hint and re-execute the UPDATE statement, remove the hint and specify COL2 IS NOT NULL in the WHERE clause, modify the table columns to add a NOT NULL constraint and try again with no HASH_AJ hint.  The script follows:

SPOOL ANTIJOIN_TEST11.TXT
SET LINESIZE 150
SET PAGESIZE 2000

UPDATE /*+ GATHER_PLAN_STATISTICS */
  T1
SET
  COL1 = NULL
WHERE
  COL2 NOT IN (
    SELECT /*+ HASH_AJ */
      T2.COL2
    FROM
      T2);

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

ROLLBACK;

ALTER SYSTEM FLUSH BUFFER_CACHE;
ALTER SYSTEM FLUSH BUFFER_CACHE;

ALTER SESSION SET TRACEFILE_IDENTIFIER = 'ANTIJOIN_TEST2';

UPDATE /*+ HASH_AJ GATHER_PLAN_STATISTICS */
  T1
SET
  COL1 = NULL
WHERE
  COL2 NOT IN (
    SELECT /*+  */
      T2.COL2
    FROM
      T2);

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

ROLLBACK;

ALTER SYSTEM FLUSH BUFFER_CACHE;
ALTER SYSTEM FLUSH BUFFER_CACHE;
ALTER SESSION SET TRACEFILE_IDENTIFIER = 'ANTIJOIN_TEST3';

UPDATE /*+ GATHER_PLAN_STATISTICS */
  T1
SET
  T1.COL1 = NULL
WHERE
  COL2 NOT IN (
    SELECT /*+  */
      T2.COL2
    FROM
      T2
    WHERE
      T2.COL2 IS NOT NULL)
  AND T1.COL2 IS NOT NULL;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

ROLLBACK;

ALTER TABLE T1 MODIFY COL2 NOT NULL;
ALTER TABLE T2 MODIFY COL2 NOT NULL;

ALTER SESSION SET TRACEFILE_IDENTIFIER = 'ANTIJOIN_TEST4';

EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T1',ESTIMATE_PERCENT=>NULL,NO_INVALIDATE=>FALSE)
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>USER,TABNAME=>'T2',ESTIMATE_PERCENT=>NULL,NO_INVALIDATE=>FALSE)

ALTER SYSTEM FLUSH BUFFER_CACHE;
ALTER SYSTEM FLUSH BUFFER_CACHE;

ALTER SESSION SET TRACEFILE_IDENTIFIER = 'ANTIJOIN_TEST5';
 
UPDATE /*+ GATHER_PLAN_STATISTICS */
  T1
SET
  COL1 = NULL
WHERE
  COL2 NOT IN (
    SELECT /*+  */
      T2.COL2
    FROM
      T2);
 
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));
 
ROLLBACK;

How did we do on Oracle 11.1.0.7?

The first update statement had an execution plan like this:

UPDATE /*+ GATHER_PLAN_STATISTICS */   T1 SET   COL1 = NULL WHERE                                                                          
COL2 NOT IN (     SELECT /*+ HASH_AJ */       T2.COL2     FROM       T2)                                                                   

Plan hash value: 875068713   

--------------------------------------------------------------------------------------------------------------------------------------------------      
| Id  | Operation                | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem | Used-Tmp|      
--------------------------------------------------------------------------------------------------------------------------------------------------      
|   0 | UPDATE STATEMENT         |      |      1 |        |      0 |00:04:07.39 |    6898K|  85474 |  36766 |       |       |          |         |      
|   1 |  UPDATE                  | T1   |      1 |        |      0 |00:04:07.39 |    6898K|  85474 |  36766 |       |       |          |         |      
|*  2 |   HASH JOIN RIGHT ANTI NA|      |      1 |   9999K|   6666K|00:00:30.58 |   38576 |  75353 |  36766 |   196M|    10M|   46M (1)|     298K|      
|   3 |    TABLE ACCESS FULL     | T2   |      1 |     10M|     10M|00:00:10.03 |   19288 |  19278 |      0 |       |       |          |         |      
|   4 |    TABLE ACCESS FULL     | T1   |      1 |     10M|     10M|00:00:00.12 |   19288 |  19278 |      0 |       |       |          |         |      
--------------------------------------------------------------------------------------------------------------------------------------------------      

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("COL2"="T2"."COL2")                 

According to the DBMS_XPLAN output, even without the table columns declared as NOT NULL, Oracle was able to use a special form of a NULL aware hash join anti – the query completed in just over 4 minutes.  According to the 10053 trace, only the GATHER_PLAN_STATISTICS hint was recognized.  The final query after transformation follows:

SELECT 0 FROM "TESTUSER"."T2" "T2","TESTUSER"."T1" "T1" WHERE "T1"."COL2"="T2"."COL2"

Moving the HASH_AJ hint so that it is next to the GATHER_PLAN_STATISTICS hint caused the optimizer to recognize the hint, but the 10053 trace file showed that the hint was not used.  The execution plan was identical to the execution plan above, although the actual time and number of consistent/current mode gets differed slightly.

Specifying the IS NOT NULL restriction in the WHERE clause did change the execution plan to a standard HASH JOIN ANTI:

UPDATE /*+ GATHER_PLAN_STATISTICS */   T1 SET   T1.COL1 = NULL WHERE                                                                       
COL2 NOT IN (     SELECT /*+  */       T2.COL2     FROM       T2                                                                           
WHERE       T2.COL2 IS NOT NULL)   AND T1.COL2 IS NOT NULL                                                                                 

Plan hash value: 2180616727  

-----------------------------------------------------------------------------------------------------------------------------------------------         
| Id  | Operation             | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem | Used-Tmp|         
-----------------------------------------------------------------------------------------------------------------------------------------------         
|   0 | UPDATE STATEMENT      |      |      1 |        |      0 |00:04:41.32 |    6926K|  84797 |  36766 |       |       |          |         |         
|   1 |  UPDATE               | T1   |      1 |        |      0 |00:04:41.32 |    6926K|  84797 |  36766 |       |       |          |         |         
|*  2 |   HASH JOIN RIGHT ANTI|      |      1 |   9999K|   6666K|00:00:57.74 |   38576 |  75353 |  36766 |   196M|    10M|   46M (1)|     298K|         
|*  3 |    TABLE ACCESS FULL  | T2   |      1 |     10M|     10M|00:00:00.09 |   19288 |  19278 |      0 |       |       |          |         |         
|*  4 |    TABLE ACCESS FULL  | T1   |      1 |     10M|     10M|00:00:00.12 |   19288 |  19278 |      0 |       |       |          |         |         
-----------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("COL2"="T2"."COL2")                 
   3 - filter("T2"."COL2" IS NOT NULL)            
   4 - filter("T1"."COL2" IS NOT NULL)

The number of consistent gets increased slightly, and so did the actual run time.

Let’s take a look at the statistics from the plan when the table columns have the NOT NULL constraint:

-----------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem | Used-Tmp|
-----------------------------------------------------------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT      |      |      1 |        |      0 |00:04:47.51 |    6930K|  84435 |  36766 |       |       |          |         |
|   1 |  UPDATE               | T1   |      1 |        |      0 |00:04:47.51 |    6930K|  84435 |  36766 |       |       |          |         |
|*  2 |   HASH JOIN RIGHT ANTI|      |      1 |   9999K|   6666K|00:00:29.61 |   38576 |  75353 |  36766 |   196M|    10M|   46M (1)|     298K|
|   3 |    TABLE ACCESS FULL  | T2   |      1 |     10M|     10M|00:00:00.01 |   19288 |  19278 |      0 |       |       |          |         |
|   4 |    TABLE ACCESS FULL  | T1   |      1 |     10M|     10M|00:00:00.12 |   19288 |  19278 |      0 |       |       |          |         |
-----------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("COL2"="T2"."COL2")      

The same execution plan was used again, but the number of consistent gets and actual time increased even more.  In case you are wondering, the 10053 trace file shows that the final query after transformation is this:

SELECT 0 FROM "TESTUSER"."T2" "T2","TESTUSER"."T1" "T1" WHERE "T1"."COL2"="T2"."COL2"

—-

So, Oracle 11.1.0.7 is able to take advantage of the NULL aware “HASH JOIN RIGHT ANTI NA” operation.  Does this help?  Well, let’s try the test again on Oracle 10.2.0.4 running on the same computer.  The execution plan from the 10053 trace might look something like this for the first SQL statement with the hint in the wrong location:

UPDATE /*+ GATHER_PLAN_STATISTICS */   T1 SET   COL1 = NULL WHERE   COL2 NOT
IN (     SELECT /*+ HASH_AJ */       T2.COL2     FROM       T2)

============
Plan Table
============
---------------------------------------+-----------------------------------+
| Id  | Operation            | Name    | Rows  | Bytes | Cost  | Time      |
---------------------------------------+-----------------------------------+
| 0   | UPDATE STATEMENT     |         |       |       |   50G |           |
| 1   |  UPDATE              | T1      |       |       |       |           |
| 2   |   FILTER             |         |       |       |       |           |
| 3   |    TABLE ACCESS FULL | T1      | 9766K |   76M |  5332 |  00:01:04 |
| 4   |    TABLE ACCESS FULL | T2      |     1 |     6 |  5348 |  00:01:05 |
---------------------------------------+-----------------------------------+
Predicate Information:
----------------------
2 - filter( IS NULL)
4 - filter(LNNVL("T2"."COL2"<>:B1))

Note that the execution plan includes a FILTER operation on ID 2 of the plan – that is what the OP was seeing.  The final query after transformation looked something like this:

SELECT 0 FROM "TESTUSER"."T1" "SYS_ALIAS_1" WHERE  NOT EXISTS (SELECT /*+ HASH_AJ */ 0 FROM "TESTUSER"."T2" "T2" WHERE LNNVL("T2"."COL2"<>"SYS_ALIAS_1"."COL2"))

So, what did the execution plan look like with the statistics?  I don’t know – I killed it after a couple hours.  When I killed it, the SQL statement had processed 114,905,284 consistent or current mode gets and burned through 9,763.26 seconds of CPU time.

That just means we need a more powerful server, right?  OK, let’s try another experiment on a more powerful server.  This time, we will use a 10046 trace at level 8 using the query with the IS NOT NULL condition in the WHERE clause and gradually decrease the OPTIMIZER_FEATURES_ENABLED parameter from 11.1.0.7 to 10.2.0.1 to 9.2.0 to 8.1.7.

11.1.0.7:

Cursor 7   Ver 1   Parse at 0.000000  Similar Cnt 1
|PARSEs       1|CPU S    0.000000|CLOCK S    0.000000|ROWs        0|PHY RD BLKs         0|CON RD BLKs (Mem)         0|CUR RD BLKs (Mem)         0|SHARED POOL MISs      1|
|EXECs        1|CPU S   49.077915|CLOCK S   64.402548|ROWs  6666667|PHY RD BLKs     27342|CON RD BLKs (Mem)     39170|CUR RD BLKs (Mem)   6816007|SHARED POOL MISs      0|
|FETCHs       0|CPU S    0.000000|CLOCK S    0.000000|ROWs        0|PHY RD BLKs         0|CON RD BLKs (Mem)         0|CUR RD BLKs (Mem)         0|SHARED POOL MISs      0|

UPDATE
  T1
SET
  T1.COL1 = NULL
WHERE
  COL2 NOT IN (
    SELECT /*+  */
      T2.COL2
    FROM
      T2
    WHERE
      T2.COL2 IS NOT NULL)
  AND T1.COL2 IS NOT NULL

       (Rows 0)   UPDATE  T1 (cr=39170 pr=27342 pw=27311 time=0 us)
 (Rows 6666667)    HASH JOIN RIGHT ANTI (cr=38576 pr=27342 pw=27311 time=93577 us cost=28657 size=28 card=2)
(Rows 10000000)     TABLE ACCESS FULL T2 (cr=19288 pr=0 pw=0 time=0 us cost=5290 size=60000000 card=10000000)
(Rows 10000000)     TABLE ACCESS FULL T1 (cr=19288 pr=0 pw=0 time=15717 us cost=5290 size=80000000 card=10000000)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  direct path write temp                        881        0.14          1.60
  direct path read temp                         882        0.40         12.03
  latch: checkpoint queue latch                   4        0.00          0.00
  log file switch completion                      8        0.54          1.01
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00

64.4 seconds of total run time with 49.1 seconds of CPU time – I guess that is a bit faster.  Quite a few consistent gets and current mode gets.

10.2.0.1 and 9.2.0.1 generated the same execution plan as the above, so the statistics should also be about the same.

8.1.7:

Well, we ran into a problem with this one – I killed it after a bit over an hour.  The TYPICAL execution plan looked like this:

SQL_ID  11apc6grf5fph, child number 0
-------------------------------------
UPDATE   T1 SET   T1.COL1 = NULL WHERE   COL2 NOT IN (     SELECT /*+ 
*/       T2.COL2     FROM       T2     WHERE       T2.COL2 IS NOT NULL)
  AND T1.COL2 IS NOT NULL

Plan hash value: 3288325718

---------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost  | Inst   |
---------------------------------------------------------------------
|   0 | UPDATE STATEMENT    |      |       |       |  2926 |        |
|   1 |  UPDATE             | T1   |       |       |       |   OR11 |
|*  2 |   FILTER            |      |       |       |       |        |
|*  3 |    TABLE ACCESS FULL| T1   |   500K|  3906K|  2926 |   OR11 |
|*  4 |    TABLE ACCESS FULL| T2   |     1 |     6 |  2926 |   OR11 |
---------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter( IS NULL)
   3 - filter("T1"."COL2" IS NOT NULL)
   4 - filter(("T2"."COL2"=:B1 AND "T2"."COL2" IS NOT NULL))

That darn FILTER on ID 2 is back again, like what the OP saw.  What were the execution statistics at that point?  4853.41 seconds of CPU time and 212,520,861 consistent or current mode gets.  I know, let’s throw some more hardware at it, because that is the cheap and completely safe, risk-free solution, rather than doing a root cause analysis of the problem (that has to be true, I read it on the Internet – more than once 🙂 ).





Drilling into Session Detail from the Operating System – On the Windows Platform 2

9 01 2010

January 9, 2010

In the earlier post, you saw a script output that looked something like the following:

1/8/2010 12:57:56 PM Processes: 73 Threads: 861 C. Switches: 35972 Q. Length: 6
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 9.33S Sys  Time: 25.19S Memory: 141.11MB Page File: 0.63MB
  Handle: 1444 User Time: 9.34S Sys  Time: 25.5S ElapsedTime: 70S Priority: 8 ThreadState: In Run Queue

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 103.9S Sys  Time: 0.13S Memory: 442.42MB Page File: 0.67MB
  Handle: 3520 User Time: 34.13S Sys  Time: 0.02S ElapsedTime: 69S Priority: 8 ThreadState: Running
   PID:18 SPID:3520 SID:146 SERIAL#:4 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   DECLARE   i NUMBER := 0;   STime DATE := SYSDATE; BEGIN   WHILE (SYSDATE - STime) < 0.006945 LOOP     i := i + + 0.000001;   End Loop; End;

    SQL_ID  cujkdbu2npk0x, child number 0

    DECLARE   i NUMBER := 0;   STime DATE := SYSDATE; BEGIN   WHILE
    (SYSDATE - STime) < 0.006945 LOOP     i := i + + 0.000001;   End Loop;
    End;

    NOTE: cannot fetch plan for SQL_ID: cujkdbu2npk0x, CHILD_NUMBER: 0
          Please verify value of SQL_ID and CHILD_NUMBER;
          It could also be that the plan is no longer in cursor cache (check v$sql_plan)

  Handle: 2152 User Time: 34.04S Sys  Time: 0S ElapsedTime: 69S Priority: 8 ThreadState: In Run Queue
   PID:19 SPID:2152 SID:145 SERIAL#:12 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is the long description for this number '|| TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000)))...

    SQL_ID  6hxcgpp1rd1ag, child number 0
    -------------------------------------
    INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is
    the long description for this number '||
    TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000))) FROM   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000),   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000)

    Plan hash value: 643243249

    -----------------------------------------------------------------------------------
    | Id  | Operation                          | Name | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                   |      |       |     4 (100)|          |
    |   1 |  LOAD TABLE CONVENTIONAL           |      |       |            |          |
    |   2 |   COUNT                            |      |       |            |          |
    |   3 |    MERGE JOIN CARTESIAN            |      |     1 |     4   (0)| 00:00:01 |
    |   4 |     VIEW                           |      |     1 |     2   (0)| 00:00:01 |
    |   5 |      COUNT                         |      |       |            |          |
    |   6 |       CONNECT BY WITHOUT FILTERING |      |       |            |          |
    |   7 |        FAST DUAL                   |      |     1 |     2   (0)| 00:00:01 |
    |   8 |     BUFFER SORT                    |      |     1 |     4   (0)| 00:00:01 |
    |   9 |      VIEW                          |      |     1 |     2   (0)| 00:00:01 |
    |  10 |       COUNT                        |      |       |            |          |
    |  11 |        CONNECT BY WITHOUT FILTERING|      |       |            |          |
    |  12 |         FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------

The above was echoed to the command line window and also written to a logging file.  So, how do we create this output?  Save the following on the root of the C:\ drive as CPUHighLoadProcesses.vbs:

Const adNumeric=131
Const adParamInput=1
Const adCmdText=1
Const adVarChar=200

Dim i                       'Loop counter for iterations
Dim j                       'Loop counter to find the previous stats for the thread
Dim intInstance             'Index to the instance statistics
Dim IntOldCSPerSec          'Previous value for Context Switches Per Second
Dim intOldProcesses         'Previous value for the number of processes
Dim intOldThreads           'Previous value for the number of threads
Dim strSQL                  'SQL to check WMI Win32_PerfRawData_PerfOS_System
Dim strSQL2                 'SQL to find the Oracle processes using WMI Win32_Process
Dim strSQL3                 'SQL to drill into the threads in the Oracle processes using WMI Win32_Thread
Dim strSQL4                 'SQL to find the Oracle session, SQL statement, and execution plan
Dim strOut                  'Data to be written to the text file
Dim strComputer             'Oracle database server name, or . for the current computer
Dim sglUMTime(20)           'User mode time in seconds for the Oracle instance
Dim sglKMTime(20)           'Kernel mode time in seconds for the Oracle instance
Dim sglWorkingSet(20)       'Working set memory size for the Oracle instance
Dim sglPageFileUsage(20)    'Page/swap file usage for the Oracle instance
Dim sglOUMTime(20)          'Previous user mode time in seconds for the Oracle instance
Dim sglOKMTime(20)          'Previous kernel mode time in seconds for the Oracle instance
Dim sglOldWorkingSet(20)    'Previous working set memory size for the Oracle instance
Dim sglOldPageFileUsage(20) 'Previous page/swap file usage for the Oracle instance
Dim objWMIService           'An object used to pass in the WMI calls
Dim colItems                'Collection of processes running on the server
Dim objItem                 'An individual process running on the server
Dim colThreads              'Collection of threads running in a process
Dim objThread               'An individual thread running in a process
Dim intThread               'Index to the previous values for the thread's statistics
Dim intThreadCount(20)      'Maximum previous thread index for the instance
Dim intThreadH(20,600)      'Thread handle
Dim sglTUMTime(20,600)      'User mode time in seconds for the thread
Dim sglTKMTime(20,600)      'Kernel mode time in seconds for the thread
Dim sglTETime(20,600)       'Elapsed time in seconds for the thread
Dim adsFile                 'ADO stream object used to write out the log file
Dim snpData                 'ADO recordset used to query V$SESSION/V$SQL
Dim comData                 'ADO command object used to permit passing in bind variables for the V$SESSION/V$SQL query
Dim snpDataPlan             'ADO recordset used to retrieve the execution plan for the session's SQL statement
dim comDataPlan             'ADO command object used to permit passing in bind variables for the execution plan
Dim dbDatabase              'ADO database connection object
Dim intCheckIterations      'Number of times to check the instances
Dim intDelayIterations      'Number of seconds to delay between iterations
Dim sglThreadBusyPercent    'Percentage of the seconds in the iteration delay does a session need to consume to be examined

Set snpData = CreateObject("ADODB.Recordset")
Set comData = CreateObject("ADODB.Command")
Set snpDataPlan = CreateObject("ADODB.Recordset")
Set comDataPlan = CreateObject("ADODB.Command")
Set dbDatabase = CreateObject("ADODB.Connection")

strUsername = "MyUsername"
strPassword = "MyPassword"
strDatabase = "MyDB"

intCheckIterations = 10
intDelayIterations = 60
sglThreadBusyPercent = 0.50  '50%

dbDatabase.ConnectionString = "Provider=OraOLEDB.Oracle;Data Source=" & strDatabase & ";User ID=" & strUsername & ";Password=" & strPassword & ";"
dbDatabase.Open

'Should verify that the connection attempt was successful, but I will leave that for someone else to code
On Error Resume Next  'Allow continuing the script if an error happens

'Prepare the ADO Stream to write the data to the text file
Set adsFile = CreateObject("ADODB.Stream")
adsFile.Type = 2
adsFile.Charset = "iso-8859-1"
adsFile.Open

'Prepare the SQL statements
strSQL = "SELECT * FROM Win32_PerfRawData_PerfOS_System"
strSQL2 = "SELECT * FROM Win32_Process Where Name like 'Oracle%'"
strSQL3 = "SELECT * FROM Win32_Thread Where ProcessHandle="

With comData
  strSQL4 = "SELECT" & VBCrLf
  strSQL4 = strSQL4 & "  P.PID," & VBCrLf
  strSQL4 = strSQL4 & "  P.SPID," & VBCrLf
  strSQL4 = strSQL4 & "  S.SID," & VBCrLf
  strSQL4 = strSQL4 & "  S.SERIAL#," & VBCrLf
  strSQL4 = strSQL4 & "  NVL(S.USERNAME,' ') USERNAME," & VBCrLf
  strSQL4 = strSQL4 & "  NVL(S.MACHINE,' ') MACHINE," & VBCrLf
  strSQL4 = strSQL4 & "  NVL(S.PROGRAM,' ') PROGRAM," & VBCrLf
  strSQL4 = strSQL4 & "  S.SQL_ID," & VBCrLf
  strSQL4 = strSQL4 & "  S.SQL_CHILD_NUMBER," & VBCrLf
  strSQL4 = strSQL4 & "  NVL(SQL.SQL_TEXT,' ') SQL_TEXT" & VBCrLf
  strSQL4 = strSQL4 & "FROM" & VBCrLf
  strSQL4 = strSQL4 & "  V$PROCESS P," & VBCrLf
  strSQL4 = strSQL4 & "  V$SESSION S," & VBCrLf
  strSQL4 = strSQL4 & "  V$SQL SQL" & VBCrLf
  strSQL4 = strSQL4 & "WHERE" & VBCrLf
  strSQL4 = strSQL4 & "  P.SPID=?" & VBCrLf
  strSQL4 = strSQL4 & "  AND P.ADDR=S.PADDR" & VBCrLf
  strSQL4 = strSQL4 & "  AND S.SQL_ID = SQL.SQL_ID(+)" & VBCrLf
  strSQL4 = strSQL4 & "  AND S.SQL_CHILD_NUMBER = SQL.CHILD_NUMBER(+)" & VBCrLf
  strSQL4 = strSQL4 & "ORDER BY" & VBCrLf
  strSQL4 = strSQL4 & "  S.USERNAME," & VBCrLf
  strSQL4 = strSQL4 & "  P.PROGRAM"

  .Parameters.Append .CreateParameter("spid", adNumeric, adParamInput, 12, 0)
  .CommandText = strSQL4
  .CommandType = adCmdText
  .CommandTimeout = 30
  .ActiveConnection = dbDatabase
End With

With comDataPlan
  strSQL4 = "SELECT" & VBCrLf
  strSQL4 = strSQL4 & "  *" & VBCrLf
  strSQL4 = strSQL4 & "FROM" & VBCrLf
  strSQL4 = strSQL4 & "  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(?, ?, 'TYPICAL'))" & VBCrLf

  .Parameters.Append .CreateParameter("sql_id", adVarchar, adParamInput, 20, "")
  .Parameters.Append .CreateParameter("child_number", adNumeric, adParamInput, 12, 0)
  .CommandText = strSQL4
  .CommandType = adCmdText
  .CommandTimeout = 30
  .ActiveConnection = dbDatabase
End With

strComputer = "."  ' the . indicates the local computer
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\CIMV2")

IntOldCSPerSec = 0

For intInstance = 1 to 20
    sglOUMTime(intInstance) = 0
    sglOKMTime(intInstance) = 0
    sglOldWorkingSet(intInstance) = 0
    sglOldPageFileUsage(intInstance) = 0
    intThreadCount(intInstance) = 0
    intThreadH(intInstance,1) = 0
    sglTUMTime(intInstance,1) = 0
    sglTKMTime(intInstance,1) = 0
    sglTETime(intInstance,1) = 0
Next

For i = 1 to intCheckIterations 'Number of iterations
  Set colItems = objWMIService.ExecQuery(strSQL,"WQL",48)

  For Each objItem in colItems
    strOut = Now() & " Processes: " & objItem.Processes
    strOut = strOut & " Threads: " & objItem.Threads
    strOut = strOut & " C. Switches: " & objItem.ContextSwitchesPersec - IntOldCSPerSec
    strOut = strOut & " Q. Length: " & objItem.ProcessorQueueLength

    'Write to screen
    Wscript.Echo strOut

    'Write to log file
    adsFile.WriteText(strOut & vbCrLf)

    IntOldCSPerSec = objItem.ContextSwitchesPersec
    intOldProcesses = objItem.Processes
    intOldThreads = objItem.Threads
  Next

  Set colItems = Nothing
  Set colItems = objWMIService.ExecQuery(strSQL2,"WQL",48)

  intInstance = 0
  For Each objItem in colItems
    intInstance = intInstance + 1
    sglUMTime(intInstance) = Round(objItem.UserModeTime/10000000, 2)
    sglKMTime(intInstance) = Round(objItem.KernelModeTime/10000000, 2)
    sglWorkingSet(intInstance) = Round(objItem.WorkingSetSize/1048576, 2)
    sglPageFileUsage(intInstance) = Round(objItem.PageFileUsage/1048576, 2)

    strOut = "Instance: " & objItem.CommandLine & vbCrLf
    strOut = strOut & " User Time: " & Round(sglUMTime(intInstance) - sglOUMTime(intInstance),2) & "S"
    strOut = strOut & " Sys  Time: " & Round(sglKMTime(intInstance) - sglOKMTime(intInstance),2) & "S"
    strOut = strOut & " Memory: " & sglWorkingSet(intInstance) & "MB"
    strOut = strOut & " Page File: " & sglPageFileUsage(intInstance) & "MB" & vbCrLf

    Set colThreads = Nothing
    Set colThreads = objWMIService.ExecQuery(strSQL3 & cStr(objItem.ProcessID),"WQL",48)

    For Each objThread in colThreads
      'Find the statistics for thread from the previous iteration's statistics
      intThread = intThreadCount(intInstance) + 1
      For j = 1 to intThreadCount(intInstance)
        If intThreadH(intInstance,j) = objThread.Handle Then
          intThread = j
          Exit For
        End If
      Next

      If intThreadH(intInstance,intThread) = 0 Then
        'This is a new thread that was not captured before
        intThreadH(intInstance,intThread) = objThread.Handle
        If intThreadCount(intInstance) < intThread Then
          intThreadCount(intInstance) = intThread
        End If
      End If

      If (cSng(Round(objThread.UserModeTime/1000,2)-sglTUMTime(intInstance,intThread)+ _
          Round(objThread.KernelModeTime/1000,2)-sglTKMTime(intInstance,intThread)) / _
          intDelayIterations) >= sglThreadBusyPercent Then
        strOut = strOut & "  Handle: " & cStr(objThread.Handle)
        strOut = strOut & " User Time: " & Round((Round(objThread.UserModeTime/1000,2)-sglTUMTime(intInstance,intThread)),2) & "S"
        strOut = strOut & " Sys  Time: " & Round((Round(objThread.KernelModeTime/1000,2)-sglTKMTime(intInstance,intThread)),2) & "S"
        strOut = strOut & " ElapsedTime: " & Round((Round(objThread.ElapsedTime/1000,2)-sglTETime(intInstance,intThread)),2) & "S"
        strOut = strOut & " Priority: " & objThread.Priority
        strOut = strOut & " ThreadState:"
        Select Case objThread.ThreadState
          Case 0
            strOut = strOut & " Initialized"
          Case 1
            strOut = strOut & " In Run Queue"
          Case 2
            strOut = strOut & " Running"
          Case 3
            strOut = strOut & " Preparing to Run"
          Case 4
            strOut = strOut & " Terminated"
          Case 5
            strOut = strOut & " Idle"
          Case 6
            strOut = strOut & " Non-CPU Wait Event:"
            'See http://msdn.microsoft.com/en-us/library/aa394494(VS.85).aspx
            Select Case objThread.ThreadWaitReason
              Case 0
                strOut = strOut & "Executive"
              Case 1
                strOut = strOut & "FreePage"
              Case 2
                strOut = strOut & "PageIn"
              Case 3
                strOut = strOut & "PoolAllocation"
              Case 4
                strOut = strOut & "ExecutionDelay"
              Case 5
                strOut = strOut & "FreePage"
              Case 6
                strOut = strOut & "PageIn"
              Case 7
                strOut = strOut & "Executive"
              Case 8
                strOut = strOut & "FreePage"
              Case 9
                strOut = strOut & "PageIn"
              Case 10
                strOut = strOut & "PoolAllocation"
              Case 11
                strOut = strOut & "ExecutionDelay"
              Case 12
                strOut = strOut & "FreePage"
              Case 13
                strOut = strOut & "PageIn"
              Case 14
                strOut = strOut & "EventPairHigh"
              Case 15
                strOut = strOut & "EventPairLow"
              Case 16
                strOut = strOut & "LPCReceive"
              Case 17
                strOut = strOut & "LPCReply"
              Case 18
                strOut = strOut & "VirtualMemory"
              Case 19
                strOut = strOut & "PageOut"
              Case 20
                strOut = strOut & "Unknown"
              Case Else
                strOut = strOut & objThread.ThreadWaitReason
            End Select
          Case 7
            strOut = strOut & " Unknown"
          Case Else
            strOut = strOut & objThread.ThreadState
        End Select       

        strOut = strOut & vbCrLf
        comData("spid") = objThread.Handle
        Set snpData = comData.Execute

        If Not(snpData Is Nothing) Then
          If Not(snpData.EOF) Then
            strOut = strOut & "   PID:" & snpData("pid")
            strOut = strOut & " SPID:" & snpData("spid")
            strOut = strOut & " SID:" & snpData("sid")
            strOut = strOut & " SERIAL#:" & snpData("serial#")
            strOut = strOut & " USERNAME:" & snpData("username")
            strOut = strOut & " MACHINE:" & snpData("machine")
            strOut = strOut & " PROGRAM:" & snpData("program") & vbCrLf
            strOut = strOut & "   " & snpData("sql_text") & vbCrLf

            If Not(IsNull(snpData("sql_id"))) Then
              comDataPlan("sql_id") = snpData("sql_id")
              comDataPlan("child_number") = snpData("sql_child_number")
              Set snpDataPlan = comDataPlan.Execute

              If Not(snpDataPlan Is Nothing) Then
                strOut = strOut & vbCrLf
                Do While Not(snpDataPlan.EOF)
                  strOut = strOut & "    " & snpDataPlan(0) & vbCrLf
                  snpDataPlan.MoveNext
                Loop
                snpDataPlan.Close
              End If
              strOut = strOut & vbCrLf
            End If
          End If
          snpData.Close 
        End If
      End If

      sglTUMTime(intInstance,intThread) = Round(objThread.UserModeTime/1000,2)
      sglTKMTime(intInstance,intThread) = Round(objThread.KernelModeTime/1000,2)
      sglTETime(intInstance,intThread) = Round(objThread.ElapsedTime/1000,2)
    Next
    strOut = strOut & vbCrLf

    'Write to screen
    Wscript.Echo strOut

    'Write to log file
    adsFile.WriteText(strOut & vbCrLf)

    sglOUMTime(intInstance) = Round(objItem.UserModeTime/10000000, 2)
    sglOKMTime(intInstance) = Round(objItem.KernelModeTime/10000000, 2)
    sglOldWorkingSet(intInstance) = Round(objItem.WorkingSetSize/1048576, 2)
    sglOldPageFileUsage(intInstance) = Round(objItem.PageFileUsage/1048576, 2)
  Next

  'Wait intDelayIterations seconds before sampling again
  Wscript.Sleep intDelayIterations * 1000
Next

adsFile.SaveToFile "C:\CPUHighLoadProcesses.txt", 2 
adsFile.Close

dbDatabase.Close

Set snpData = Nothing
Set comData = Nothing
Set snpDataPlan = Nothing
Set comDataPlan = Nothing
Set dbDatabase = Nothing
Set adsFile = Nothing

If you decide to use the above script, test it very carefully.  There may be one or two typos or logic errors.  If you have administrator rights on the server, the script can be executed remotely (it does not need to be run directly on the server).  WMI queries are given a very low priority, so if the CPU run queue is long, the script may appear to hang for a long time.  To run the script, open a command line window and type:

cscript C:\CPUHighLoadProcesses.vbs

WMI queries are very powerful, as demonstrated by the above script.

Update: 1.5 hours before this blog article is scheduled to appear:

{RANT}

After I wrote the above script and this and the previous blog articles I stumbled upon the following very recent news article that states the following:
dba-oracle.com/t_display_background_processes_windows.htm

“In Windows, the ‘thread’ model is used, and Oracle dispatches his own background tasks within the domain of the single process, oracle.exe.  Hence, you cannot see any background processes from the Windows OS (but you can see listener process and parallel query slaves).”

OK, forget about my script, because the quote directly above states that it is not possible.  Someone please call Oracle Corp. and tell them that their Oracle Administration Assistant for Windows needs to be removed from the documentation because what it shows is simply not possible. Did I mention that I dislike being confused?

{/RANT}





Drilling into Session Detail from the Operating System – On the Windows Platform

9 01 2010

January 9, 2010

(Forward to the Follow-Up Article)

As you might be aware, Oracle running on Unix/Linux uses a process model, while Oracle running on Windows uses a thread model.  With the process model it is fairly easy to use operating system tools, such as ps or top, to monitor the performance of individual database sessions, and then the DBA can use that information to manually drill back into what the high utilization sessions are doing.  Unfortunately, with the thread model, that procedure is impossible.  Unless, of course, you know a trick.

In one of the two chapters of the Expert Oracle Practices book, Randolf and I stated the following:

“In the case of performance problems caused by Oracle-related processes, consider drilling into the Oracle process activity by using the operating system process identifier (PID) to search V$PROCESS for a matching PID, and then join to V$SESSION on V$PROCESS.ADDR=V$SESSION.PADDR.”

I think that we also stated somewhere in the chapters that WMI queries could be used to drill into the session level activity on the Windows platform.  We did not provide a script to demonstrate the process – the chapters were running long by a couple pages (OK more than a couple if you have seen the chapters), so we did not bother to construct a demonstration.  I played with the idea a little, and then decided that the 10 to 20 pages it would take to describe the process could be better used for something else.

So, let’s play a game of bury the poor Windows box (this happens to be a computer with a dual core CPU).  It would be neat, if we have 20 database instances running on this poor Windows box, to play a game of Whose Got My CPU (R).  So, it would be neat if we could send something like this out to a command line window:

1/8/2010 12:47:18 PM Processes: 73 Threads: 880 C. Switches: 10573606 Q. Length: 7
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 668.4S Sys  Time: 295.9S Memory: 275.59MB Page File: 0.63MB
  Handle: 1444 User Time: 663.32S Sys  Time: 296.99S ElapsedTime: 3358S Priority: 8 ThreadState: In Run Queue

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 2959.84S Sys  Time: 11.73S Memory: 539.5MB Page File: 0.67MB
  Handle: 3520 User Time: 1081.91S Sys  Time: 0.92S ElapsedTime: 3238S Priority: 8 ThreadState: Running

The above states that at 12:47 there were 73 processes running on this poor Windows box with 880 process threads.  There were 10,573,606 context switches over some period of time, and the run queue length is currently 7 (could this be a sign of a problem on a computer with a single dual core CPU – no, can’t be – someone on the Internet said that pushing the CPUs to 100% utilization is desirable, and because it is on the Internet, it must be true).

Next, the above output shows that an Oracle 10.2.0.x database instance was found (OR10).  This instance had consumed 668.4 seconds of CPU time in user mode, and 295.9 seconds of CPU time in system (kernel) mode – that system mode time seems to be high compared to the user mode time.  The instance was using about 276MB of memory. A single thread representing a dedicated connection was created 33,585 seconds earlier (that might be a calculation error – looks like I should have divided that number by 10).  That thread has used 663.32 seconds of user mode CPU time, and 296.99 seconds of system (kernel) mode CPU time.  The thread had a scheduling priority of 8 assigned to it and at the time was sitting in the run queue waiting for the CPU to become available.

Next, the above output shows that an Oracle 11.1.0.x database instance was found (OR11).  This instance consumed 2959.84 seconds of CPU time in user mode, and 11.73 second of CPU time in system (kernel) mode.  The instance was using about 540MB of memory.   A single thread representing a dedicated session was created 32,385 seconds earlier.  That thread had used 1081.91 seconds of user mode CPU time (about 1/3 of the total for the instance) and 0.92 seconds of system (kernel) mode CPU time.  The thread had a scheduling priority of 8 assigned to it and at the time was running on the CPU (something else must have been running on the other CPU core – possibly the script that collected this data).

OK, the above is kind of neat, but what are the sessions doing that caused the CPU usage?  I don’t have the SID and SERIAL# for the sessions, so I can’t enable a 10046 trace.  It will take too long to fire up a GUI of some sort to see what is happening.  I wonder if I can do anything with the handle number that was output?  Well, I read the chapters that Randolf and I wrote, and that Handle just so happens to be treated like the PID on Oracle platforms that use a process model.  So, I could do all kinds of interesting things once I resolve the displayed Handle to a SID.  For instance, I could do something like this:

1/8/2010 12:47:18 PM Processes: 73 Threads: 880 C. Switches: 10573606 Q. Length: 7
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 668.4S Sys  Time: 295.9S Memory: 275.59MB Page File: 0.63MB
  Handle: 1444 User Time: 663.32S Sys  Time: 296.99S ElapsedTime: 3358S Priority: 8 ThreadState: In Run Queue

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 2959.84S Sys  Time: 11.73S Memory: 539.5MB Page File: 0.67MB
  Handle: 3520 User Time: 1081.91S Sys  Time: 0.92S ElapsedTime: 3238S Priority: 8 ThreadState: Running
   PID:18 SPID:3520 SID:146 SERIAL#:4 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   SELECT   TOP_LEVEL_PART_ID, REQ.PURC_PART_ID, REQ.TOTAL_QTY,   REQ.TOTAL_QTY*TPPD.INCREASE "Increase",   REQ.R1_PART_ID, REQ.R2_PART_ID,...

    SQL_ID  3p1v051atxt1z, child number 0
    -------------------------------------
    SELECT   TOP_LEVEL_PART_ID, REQ.PURC_PART_ID, REQ.TOTAL_QTY,  
    REQ.TOTAL_QTY*TPPD.INCREASE "Increase",   REQ.R1_PART_ID,
    REQ.R2_PART_ID, REQ.R3_PART_ID,   REQ.R4_PART_ID, REQ.R5_PART_ID,
    REQ.R6_PART_ID,   REQ.R1_CALC_QTY, REQ.R2_CALC_QTY, REQ.R3_CALC_QTY,  
    REQ.R4_CALC_QTY, REQ.R5_CALC_QTY, REQ.R6_CALC_QTY FROM   (SELECT    
    TOP_LEVEL_PART_ID,     DECODE(R6_PART_ID,NULL,      
    DECODE(R5_PART_ID,NULL,         DECODE(R4_PART_ID,NULL,          
    DECODE(R3_PART_ID,NULL,             DECODE(R2_PART_ID,NULL,            
           R1_PART_ID,R2_PART_ID),                  R3_PART_ID),           
        R4_PART_ID),              R5_PART_ID),            R6_PART_ID)
    PURC_PART_ID,     NVL(R1_CALC_QTY,0)*NVL(R2_CALC_QTY,1)*NVL(R3_CALC_QTY,
    1)*NVL(R4_CALC_QTY,1)*NVL(R5_CALC_QTY,1)*NVL(R6_CALC_QTY,1) TOTAL_QTY, 
       R1_PART_ID, R1_CALC_QTY, R2_PART_ID, R2_CALC_QTY, R3_PART_ID,
    R3_CALC_QTY,     R4_PART_ID, R4_CALC_QTY, R5_PART_ID, R5_CALC_QTY,
    R6_PART_ID, R6_CALC_QTY   FROM     (SELECT       PL.PART_ID TOP_LEVE

    Plan hash value: 3313542492

    ---------------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                                       | Name                 | Rows  | Bytes | Cost (%CPU)| Time     | Inst   |
    ---------------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                                |                      |       |       |   101 (100)|          |        |
    |   1 |  VIEW                                           | VM_NWVW_1            |     1 |   225 |   101   (2)| 00:00:02 |   OR11 |
    |   2 |   HASH UNIQUE                                   |                      |     1 |   498 |   101   (2)| 00:00:02 |        |
    |   3 |    NESTED LOOPS OUTER                           |                      |     1 |   498 |   100   (1)| 00:00:02 |        |
    |*  4 |     FILTER                                      |                      |       |       |            |          |        |
    |   5 |      NESTED LOOPS OUTER                         |                      |     1 |   474 |    99   (2)| 00:00:02 |        |
    |   6 |       NESTED LOOPS OUTER                        |                      |     1 |   435 |    96   (2)| 00:00:02 |        |
    |   7 |        NESTED LOOPS OUTER                       |                      |     1 |   411 |    95   (2)| 00:00:02 |        |
    |*  8 |         FILTER                                  |                      |       |       |            |          |        |
    |   9 |          NESTED LOOPS OUTER                     |                      |     1 |   387 |    94   (2)| 00:00:02 |        |
    |* 10 |           FILTER                                |                      |       |       |            |          |        |
    |  11 |            NESTED LOOPS OUTER                   |                      |     1 |   348 |    91   (2)| 00:00:02 |        |
    |  12 |             NESTED LOOPS OUTER                  |                      |     1 |   309 |    88   (2)| 00:00:02 |        |
    |  13 |              NESTED LOOPS OUTER                 |                      |     1 |   285 |    87   (2)| 00:00:02 |        |
    |  14 |               NESTED LOOPS OUTER                |                      |     1 |   261 |    86   (2)| 00:00:02 |        |
    |* 15 |                FILTER                           |                      |       |       |            |          |        |
    |  16 |                 NESTED LOOPS OUTER              |                      |     1 |   237 |    85   (2)| 00:00:02 |        |
    |* 17 |                  FILTER                         |                      |       |       |            |          |        |
    |  18 |                   NESTED LOOPS OUTER            |                      |     1 |   198 |    82   (2)| 00:00:01 |        |
    |  19 |                    NESTED LOOPS                 |                      |     1 |   159 |    79   (2)| 00:00:01 |        |
    |  20 |                     NESTED LOOPS                |                      |     1 |   118 |    76   (2)| 00:00:01 |        |
    |  21 |                      MERGE JOIN CARTESIAN       |                      |     1 |    94 |    71   (2)| 00:00:01 |        |
    |* 22 |                       TABLE ACCESS FULL         | TEMP_PART_PRICE_DATE |     1 |    85 |     2   (0)| 00:00:01 |   OR11 |
    |  23 |                       BUFFER SORT               |                      |  7016 | 63144 |    69   (2)| 00:00:01 |        |
    |* 24 |                        TABLE ACCESS FULL        | CUSTOMER_ORDER       |  7016 | 63144 |    69   (2)| 00:00:01 |   OR11 |
    |* 25 |                      TABLE ACCESS BY INDEX ROWID| CUST_ORDER_LINE      |     1 |    24 |     5   (0)| 00:00:01 |   OR11 |
    |* 26 |                       INDEX RANGE SCAN          | SYS_C0028623         |     9 |       |     1   (0)| 00:00:01 |   OR11 |
    |  27 |                     TABLE ACCESS BY INDEX ROWID | REQUIREMENT          |     1 |    41 |     3   (0)| 00:00:01 |   OR11 |
    |* 28 |                      INDEX RANGE SCAN           | X_REQUIREMENT_5      |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  29 |                    TABLE ACCESS BY INDEX ROWID  | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 30 |                     INDEX RANGE SCAN            | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  31 |                  TABLE ACCESS BY INDEX ROWID    | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 32 |                   INDEX RANGE SCAN              | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 33 |                TABLE ACCESS BY INDEX ROWID      | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 34 |                 INDEX UNIQUE SCAN               | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 35 |               TABLE ACCESS BY INDEX ROWID       | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 36 |                INDEX UNIQUE SCAN                | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 37 |              TABLE ACCESS BY INDEX ROWID        | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 38 |               INDEX UNIQUE SCAN                 | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |  39 |             TABLE ACCESS BY INDEX ROWID         | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 40 |              INDEX RANGE SCAN                   | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  41 |           TABLE ACCESS BY INDEX ROWID           | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 42 |            INDEX RANGE SCAN                     | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 43 |         TABLE ACCESS BY INDEX ROWID             | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 44 |          INDEX UNIQUE SCAN                      | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 45 |        TABLE ACCESS BY INDEX ROWID              | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 46 |         INDEX UNIQUE SCAN                       | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |  47 |       TABLE ACCESS BY INDEX ROWID               | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 48 |        INDEX RANGE SCAN                         | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 49 |     TABLE ACCESS BY INDEX ROWID                 | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 50 |      INDEX UNIQUE SCAN                          | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    ---------------------------------------------------------------------------------------------------------------------------------

    Predicate Information (identified by operation id):
    ---------------------------------------------------

       4 - filter(("TPPD"."PART_ID"=DECODE(DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID"
                  ,NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'N
                  ONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)),NULL,NULL,DECODE
                  (NVL("P5"."FABRICATED",'NONE'),'Y',"R6"."PART_ID",NULL)),NULL,DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NO
                  NE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(N
                  VL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",
                  NULL)),NULL,DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."
                  FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NU
                  LL,DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NON
                  E'),'Y',"R3"."PART_ID",NULL)),NULL,DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,"R"."PART_ID"
                  ,DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL)),DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_
                  ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL))),DECODE(DECODE(DECODE(NVL("P"."FABRICA
                  TED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,
                  DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL))),DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),
                  'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P
                  3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)
                  )),DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FA
                  BRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL
                  ,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P5"."FABRICATED",'NONE'),'Y',"
                  R6"."PART_ID",NULL))) AND "R6"."SUBORD_WO_SUB_ID" IS NULL))
       8 - filter("R5"."SUBORD_WO_SUB_ID" IS NULL)
      10 - filter("R4"."SUBORD_WO_SUB_ID" IS NULL)
      15 - filter("R3"."SUBORD_WO_SUB_ID" IS NULL)
      17 - filter("R2"."SUBORD_WO_SUB_ID" IS NULL)
      22 - filter("TPPD"."INCREASE"<>0)
      24 - filter(("CO"."STATUS"='C' OR "CO"."STATUS"='F' OR "CO"."STATUS"='P' OR "CO"."STATUS"='R' OR "CO"."STATUS"='U'))
      25 - filter("COL"."DESIRED_SHIP_DATE">SYSDATE@!-365)
      26 - access("CO"."ID"="COL"."CUST_ORDER_ID")
      28 - access("R"."WORKORDER_TYPE"='M' AND "COL"."PART_ID"="R"."WORKORDER_BASE_ID" AND "R"."WORKORDER_LOT_ID"='0' AND
                  "R"."WORKORDER_SPLIT_ID"='0' AND "R"."SUBORD_WO_SUB_ID" IS NULL)
      30 - access("R2"."WORKORDER_TYPE"='M' AND "R"."PART_ID"="R2"."WORKORDER_BASE_ID" AND "R2"."WORKORDER_LOT_ID"='0')
      32 - access("R3"."WORKORDER_TYPE"='M' AND "R2"."PART_ID"="R3"."WORKORDER_BASE_ID" AND "R3"."WORKORDER_LOT_ID"='0')
      33 - filter("P3"."FABRICATED"='Y')
      34 - access("R3"."PART_ID"="P3"."ID")
      35 - filter("P"."FABRICATED"='Y')
      36 - access("R"."PART_ID"="P"."ID")
      37 - filter("P2"."FABRICATED"='Y')
      38 - access("R2"."PART_ID"="P2"."ID")
      40 - access("R4"."WORKORDER_TYPE"='M' AND "R3"."PART_ID"="R4"."WORKORDER_BASE_ID" AND "R4"."WORKORDER_LOT_ID"='0')
      42 - access("R5"."WORKORDER_TYPE"='M' AND "R4"."PART_ID"="R5"."WORKORDER_BASE_ID" AND "R5"."WORKORDER_LOT_ID"='0')
      43 - filter("P4"."FABRICATED"='Y')
      44 - access("R4"."PART_ID"="P4"."ID")
      45 - filter("P5"."FABRICATED"='Y')
      46 - access("R5"."PART_ID"="P5"."ID")
      48 - access("R6"."WORKORDER_TYPE"='M' AND "R5"."PART_ID"="R6"."WORKORDER_BASE_ID" AND "R6"."WORKORDER_LOT_ID"='0')
      49 - filter("P6"."FABRICATED"='Y')
      50 - access("R6"."PART_ID"="P6"."ID")

  Handle: 2152 User Time: 983.26S Sys  Time: 2.74S ElapsedTime: 3209S Priority: 8 ThreadState: In Run Queue
   PID:19 SPID:2152 SID:145 SERIAL#:12 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is the long description for this number '|| TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000)))...

    SQL_ID  6hxcgpp1rd1ag, child number 0
    -------------------------------------
    INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is
    the long description for this number '||
    TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000))) FROM   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000),   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000)

    Plan hash value: 643243249

    -----------------------------------------------------------------------------------
    | Id  | Operation                          | Name | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                   |      |       |     4 (100)|          |
    |   1 |  LOAD TABLE CONVENTIONAL           |      |       |            |          |
    |   2 |   COUNT                            |      |       |            |          |
    |   3 |    MERGE JOIN CARTESIAN            |      |     1 |     4   (0)| 00:00:01 |
    |   4 |     VIEW                           |      |     1 |     2   (0)| 00:00:01 |
    |   5 |      COUNT                         |      |       |            |          |
    |   6 |       CONNECT BY WITHOUT FILTERING |      |       |            |          |
    |   7 |        FAST DUAL                   |      |     1 |     2   (0)| 00:00:01 |
    |   8 |     BUFFER SORT                    |      |     1 |     4   (0)| 00:00:01 |
    |   9 |      VIEW                          |      |     1 |     2   (0)| 00:00:01 |
    |  10 |       COUNT                        |      |       |            |          |
    |  11 |        CONNECT BY WITHOUT FILTERING|      |       |            |          |
    |  12 |         FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------

  Handle: 5960 User Time: 532.46S Sys  Time: 0.98S ElapsedTime: 1676S Priority: 8 ThreadState: Idle
   PID:20 SPID:5960 SID:132 SERIAL#:55 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe

Neat, for one of the database instances I am able to see the PID and SPID (in case I want to use SQLPLUS’ ORADEBUG), as well as the SID and SERIAL# (in case I want to enable a 10046 trace).  While I only retrieved the SQL statement being executed and its plan, I could have done a lot of other things also to investigate what is happening in the sessions.  I just need to find a way to log into the database to accomplish the above – note that in the above, I only logged into one database in the script. Having the CPU consumed since the thread started might be helpful, but it would be better to know how much was consumed in a particular time period, for instance the last couple minutes.  With a loop in the script, we can accomplish this task.  The script output continues (something is happening in the 10.2.0.x session, compare the user mode time with the system (kernel) mode time for this time period):

1/8/2010 12:48:31 PM Processes: 73 Threads: 866 C. Switches: 53465 Q. Length: 4
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 10.75S Sys  Time: 25.27S Memory: 275.59MB Page File: 0.63MB
  Handle: 1444 User Time: 11.37S Sys  Time: 26.88S ElapsedTime: 78S Priority: 8 ThreadState: In Run Queue

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 107.79S Sys  Time: 0.16S Memory: 543.09MB Page File: 0.67MB
  Handle: 3520 User Time: 74.24S Sys  Time: 0.02S ElapsedTime: 76S Priority: 8 ThreadState: In Run Queue
   PID:18 SPID:3520 SID:146 SERIAL#:4 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   SELECT   TOP_LEVEL_PART_ID, REQ.PURC_PART_ID, REQ.TOTAL_QTY,   REQ.TOTAL_QTY*TPPD.INCREASE "Increase",   REQ.R1_PART_ID, REQ.R2_PART_ID, REQ.R3_PART_ID,...

    SQL_ID  3p1v051atxt1z, child number 0
    -------------------------------------
    SELECT   TOP_LEVEL_PART_ID, REQ.PURC_PART_ID, REQ.TOTAL_QTY,  
    REQ.TOTAL_QTY*TPPD.INCREASE "Increase",   REQ.R1_PART_ID,
    REQ.R2_PART_ID, REQ.R3_PART_ID,   REQ.R4_PART_ID, REQ.R5_PART_ID,
    REQ.R6_PART_ID,   REQ.R1_CALC_QTY, REQ.R2_CALC_QTY, REQ.R3_CALC_QTY,  
    REQ.R4_CALC_QTY, REQ.R5_CALC_QTY, REQ.R6_CALC_QTY FROM   (SELECT    
    TOP_LEVEL_PART_ID,     DECODE(R6_PART_ID,NULL,      
    DECODE(R5_PART_ID,NULL,         DECODE(R4_PART_ID,NULL,          
    DECODE(R3_PART_ID,NULL,             DECODE(R2_PART_ID,NULL,            
           R1_PART_ID,R2_PART_ID),                  R3_PART_ID),           
        R4_PART_ID),              R5_PART_ID),            R6_PART_ID)
    PURC_PART_ID,     NVL(R1_CALC_QTY,0)*NVL(R2_CALC_QTY,1)*NVL(R3_CALC_QTY,
    1)*NVL(R4_CALC_QTY,1)*NVL(R5_CALC_QTY,1)*NVL(R6_CALC_QTY,1) TOTAL_QTY, 
       R1_PART_ID, R1_CALC_QTY, R2_PART_ID, R2_CALC_QTY, R3_PART_ID,
    R3_CALC_QTY,     R4_PART_ID, R4_CALC_QTY, R5_PART_ID, R5_CALC_QTY,
    R6_PART_ID, R6_CALC_QTY   FROM     (SELECT       PL.PART_ID TOP_LEVE

    Plan hash value: 3313542492

    ---------------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                                       | Name                 | Rows  | Bytes | Cost (%CPU)| Time     | Inst   |
    ---------------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                                |                      |       |       |   101 (100)|          |        |
    |   1 |  VIEW                                           | VM_NWVW_1            |     1 |   225 |   101   (2)| 00:00:02 |   OR11 |
    |   2 |   HASH UNIQUE                                   |                      |     1 |   498 |   101   (2)| 00:00:02 |        |
    |   3 |    NESTED LOOPS OUTER                           |                      |     1 |   498 |   100   (1)| 00:00:02 |        |
    |*  4 |     FILTER                                      |                      |       |       |            |          |        |
    |   5 |      NESTED LOOPS OUTER                         |                      |     1 |   474 |    99   (2)| 00:00:02 |        |
    |   6 |       NESTED LOOPS OUTER                        |                      |     1 |   435 |    96   (2)| 00:00:02 |        |
    |   7 |        NESTED LOOPS OUTER                       |                      |     1 |   411 |    95   (2)| 00:00:02 |        |
    |*  8 |         FILTER                                  |                      |       |       |            |          |        |
    |   9 |          NESTED LOOPS OUTER                     |                      |     1 |   387 |    94   (2)| 00:00:02 |        |
    |* 10 |           FILTER                                |                      |       |       |            |          |        |
    |  11 |            NESTED LOOPS OUTER                   |                      |     1 |   348 |    91   (2)| 00:00:02 |        |
    |  12 |             NESTED LOOPS OUTER                  |                      |     1 |   309 |    88   (2)| 00:00:02 |        |
    |  13 |              NESTED LOOPS OUTER                 |                      |     1 |   285 |    87   (2)| 00:00:02 |        |
    |  14 |               NESTED LOOPS OUTER                |                      |     1 |   261 |    86   (2)| 00:00:02 |        |
    |* 15 |                FILTER                           |                      |       |       |            |          |        |
    |  16 |                 NESTED LOOPS OUTER              |                      |     1 |   237 |    85   (2)| 00:00:02 |        |
    |* 17 |                  FILTER                         |                      |       |       |            |          |        |
    |  18 |                   NESTED LOOPS OUTER            |                      |     1 |   198 |    82   (2)| 00:00:01 |        |
    |  19 |                    NESTED LOOPS                 |                      |     1 |   159 |    79   (2)| 00:00:01 |        |
    |  20 |                     NESTED LOOPS                |                      |     1 |   118 |    76   (2)| 00:00:01 |        |
    |  21 |                      MERGE JOIN CARTESIAN       |                      |     1 |    94 |    71   (2)| 00:00:01 |        |
    |* 22 |                       TABLE ACCESS FULL         | TEMP_PART_PRICE_DATE |     1 |    85 |     2   (0)| 00:00:01 |   OR11 |
    |  23 |                       BUFFER SORT               |                      |  7016 | 63144 |    69   (2)| 00:00:01 |        |
    |* 24 |                        TABLE ACCESS FULL        | CUSTOMER_ORDER       |  7016 | 63144 |    69   (2)| 00:00:01 |   OR11 |
    |* 25 |                      TABLE ACCESS BY INDEX ROWID| CUST_ORDER_LINE      |     1 |    24 |     5   (0)| 00:00:01 |   OR11 |
    |* 26 |                       INDEX RANGE SCAN          | SYS_C0028623         |     9 |       |     1   (0)| 00:00:01 |   OR11 |
    |  27 |                     TABLE ACCESS BY INDEX ROWID | REQUIREMENT          |     1 |    41 |     3   (0)| 00:00:01 |   OR11 |
    |* 28 |                      INDEX RANGE SCAN           | X_REQUIREMENT_5      |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  29 |                    TABLE ACCESS BY INDEX ROWID  | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 30 |                     INDEX RANGE SCAN            | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  31 |                  TABLE ACCESS BY INDEX ROWID    | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 32 |                   INDEX RANGE SCAN              | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 33 |                TABLE ACCESS BY INDEX ROWID      | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 34 |                 INDEX UNIQUE SCAN               | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 35 |               TABLE ACCESS BY INDEX ROWID       | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 36 |                INDEX UNIQUE SCAN                | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 37 |              TABLE ACCESS BY INDEX ROWID        | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 38 |               INDEX UNIQUE SCAN                 | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |  39 |             TABLE ACCESS BY INDEX ROWID         | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 40 |              INDEX RANGE SCAN                   | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  41 |           TABLE ACCESS BY INDEX ROWID           | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 42 |            INDEX RANGE SCAN                     | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 43 |         TABLE ACCESS BY INDEX ROWID             | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 44 |          INDEX UNIQUE SCAN                      | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 45 |        TABLE ACCESS BY INDEX ROWID              | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 46 |         INDEX UNIQUE SCAN                       | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |  47 |       TABLE ACCESS BY INDEX ROWID               | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 48 |        INDEX RANGE SCAN                         | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 49 |     TABLE ACCESS BY INDEX ROWID                 | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 50 |      INDEX UNIQUE SCAN                          | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    ---------------------------------------------------------------------------------------------------------------------------------

    Predicate Information (identified by operation id):
    ---------------------------------------------------

       4 - filter(("TPPD"."PART_ID"=DECODE(DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID"
                  ,NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'N
                  ONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)),NULL,NULL,DECODE
                  (NVL("P5"."FABRICATED",'NONE'),'Y',"R6"."PART_ID",NULL)),NULL,DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NO
                  NE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(N
                  VL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",
                  NULL)),NULL,DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."
                  FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NU
                  LL,DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NON
                  E'),'Y',"R3"."PART_ID",NULL)),NULL,DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,"R"."PART_ID"
                  ,DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL)),DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_
                  ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL))),DECODE(DECODE(DECODE(NVL("P"."FABRICA
                  TED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,
                  DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL))),DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),
                  'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P
                  3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)
                  )),DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FA
                  BRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL
                  ,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P5"."FABRICATED",'NONE'),'Y',"
                  R6"."PART_ID",NULL))) AND "R6"."SUBORD_WO_SUB_ID" IS NULL))
       8 - filter("R5"."SUBORD_WO_SUB_ID" IS NULL)
      10 - filter("R4"."SUBORD_WO_SUB_ID" IS NULL)
      15 - filter("R3"."SUBORD_WO_SUB_ID" IS NULL)
      17 - filter("R2"."SUBORD_WO_SUB_ID" IS NULL)
      22 - filter("TPPD"."INCREASE"<>0)
      24 - filter(("CO"."STATUS"='C' OR "CO"."STATUS"='F' OR "CO"."STATUS"='P' OR "CO"."STATUS"='R' OR "CO"."STATUS"='U'))
      25 - filter("COL"."DESIRED_SHIP_DATE">SYSDATE@!-365)
      26 - access("CO"."ID"="COL"."CUST_ORDER_ID")
      28 - access("R"."WORKORDER_TYPE"='M' AND "COL"."PART_ID"="R"."WORKORDER_BASE_ID" AND "R"."WORKORDER_LOT_ID"='0' AND
                  "R"."WORKORDER_SPLIT_ID"='0' AND "R"."SUBORD_WO_SUB_ID" IS NULL)
      30 - access("R2"."WORKORDER_TYPE"='M' AND "R"."PART_ID"="R2"."WORKORDER_BASE_ID" AND "R2"."WORKORDER_LOT_ID"='0')
      32 - access("R3"."WORKORDER_TYPE"='M' AND "R2"."PART_ID"="R3"."WORKORDER_BASE_ID" AND "R3"."WORKORDER_LOT_ID"='0')
      33 - filter("P3"."FABRICATED"='Y')
      34 - access("R3"."PART_ID"="P3"."ID")
      35 - filter("P"."FABRICATED"='Y')
      36 - access("R"."PART_ID"="P"."ID")
      37 - filter("P2"."FABRICATED"='Y')
      38 - access("R2"."PART_ID"="P2"."ID")
      40 - access("R4"."WORKORDER_TYPE"='M' AND "R3"."PART_ID"="R4"."WORKORDER_BASE_ID" AND "R4"."WORKORDER_LOT_ID"='0')
      42 - access("R5"."WORKORDER_TYPE"='M' AND "R4"."PART_ID"="R5"."WORKORDER_BASE_ID" AND "R5"."WORKORDER_LOT_ID"='0')
      43 - filter("P4"."FABRICATED"='Y')
      44 - access("R4"."PART_ID"="P4"."ID")
      45 - filter("P5"."FABRICATED"='Y')
      46 - access("R5"."PART_ID"="P5"."ID")
      48 - access("R6"."WORKORDER_TYPE"='M' AND "R5"."PART_ID"="R6"."WORKORDER_BASE_ID" AND "R6"."WORKORDER_LOT_ID"='0')
      49 - filter("P6"."FABRICATED"='Y')
      50 - access("R6"."PART_ID"="P6"."ID")

  Handle: 2152 User Time: 37.25S Sys  Time: 0S ElapsedTime: 75S Priority: 8 ThreadState: In Run Queue
   PID:19 SPID:2152 SID:145 SERIAL#:12 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is the long description for this number '|| TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000)))...

    SQL_ID  6hxcgpp1rd1ag, child number 0
    -------------------------------------
    INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is
    the long description for this number '||
    TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000))) FROM   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000),   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000)

    Plan hash value: 643243249

    -----------------------------------------------------------------------------------
    | Id  | Operation                          | Name | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                   |      |       |     4 (100)|          |
    |   1 |  LOAD TABLE CONVENTIONAL           |      |       |            |          |
    |   2 |   COUNT                            |      |       |            |          |
    |   3 |    MERGE JOIN CARTESIAN            |      |     1 |     4   (0)| 00:00:01 |
    |   4 |     VIEW                           |      |     1 |     2   (0)| 00:00:01 |
    |   5 |      COUNT                         |      |       |            |          |
    |   6 |       CONNECT BY WITHOUT FILTERING |      |       |            |          |
    |   7 |        FAST DUAL                   |      |     1 |     2   (0)| 00:00:01 |
    |   8 |     BUFFER SORT                    |      |     1 |     4   (0)| 00:00:01 |
    |   9 |      VIEW                          |      |     1 |     2   (0)| 00:00:01 |
    |  10 |       COUNT                        |      |       |            |          |
    |  11 |        CONNECT BY WITHOUT FILTERING|      |       |            |          |
    |  12 |         FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------

1/8/2010 12:49:51 PM Processes: 73 Threads: 869 C. Switches: 43307 Q. Length: 6
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 11.9S Sys  Time: 27.29S Memory: 275.59MB Page File: 0.63MB
  Handle: 1444 User Time: 11.54S Sys  Time: 26.1S ElapsedTime: 76S Priority: 8 ThreadState: Running

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 117.66S Sys  Time: 0.11S Memory: 545.34MB Page File: 0.67MB
  Handle: 3520 User Time: 75.34S Sys  Time: 0.01S ElapsedTime: 76S Priority: 8 ThreadState: In Run Queue
   PID:18 SPID:3520 SID:146 SERIAL#:4 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   SELECT   TOP_LEVEL_PART_ID, REQ.PURC_PART_ID, REQ.TOTAL_QTY,   REQ.TOTAL_QTY*TPPD.INCREASE "Increase",   REQ.R1_PART_ID, REQ.R2_PART_ID, REQ.R3_PART_ID,...

    SQL_ID  3p1v051atxt1z, child number 0
    -------------------------------------
    SELECT   TOP_LEVEL_PART_ID, REQ.PURC_PART_ID, REQ.TOTAL_QTY,  
    REQ.TOTAL_QTY*TPPD.INCREASE "Increase",   REQ.R1_PART_ID,
    REQ.R2_PART_ID, REQ.R3_PART_ID,   REQ.R4_PART_ID, REQ.R5_PART_ID,
    REQ.R6_PART_ID,   REQ.R1_CALC_QTY, REQ.R2_CALC_QTY, REQ.R3_CALC_QTY,  
    REQ.R4_CALC_QTY, REQ.R5_CALC_QTY, REQ.R6_CALC_QTY FROM   (SELECT    
    TOP_LEVEL_PART_ID,     DECODE(R6_PART_ID,NULL,      
    DECODE(R5_PART_ID,NULL,         DECODE(R4_PART_ID,NULL,          
    DECODE(R3_PART_ID,NULL,             DECODE(R2_PART_ID,NULL,            
           R1_PART_ID,R2_PART_ID),                  R3_PART_ID),           
        R4_PART_ID),              R5_PART_ID),            R6_PART_ID)
    PURC_PART_ID,     NVL(R1_CALC_QTY,0)*NVL(R2_CALC_QTY,1)*NVL(R3_CALC_QTY,
    1)*NVL(R4_CALC_QTY,1)*NVL(R5_CALC_QTY,1)*NVL(R6_CALC_QTY,1) TOTAL_QTY, 
       R1_PART_ID, R1_CALC_QTY, R2_PART_ID, R2_CALC_QTY, R3_PART_ID,
    R3_CALC_QTY,     R4_PART_ID, R4_CALC_QTY, R5_PART_ID, R5_CALC_QTY,
    R6_PART_ID, R6_CALC_QTY   FROM     (SELECT       PL.PART_ID TOP_LEVE

    Plan hash value: 3313542492

    ---------------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                                       | Name                 | Rows  | Bytes | Cost (%CPU)| Time     | Inst   |
    ---------------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                                |                      |       |       |   101 (100)|          |        |
    |   1 |  VIEW                                           | VM_NWVW_1            |     1 |   225 |   101   (2)| 00:00:02 |   OR11 |
    |   2 |   HASH UNIQUE                                   |                      |     1 |   498 |   101   (2)| 00:00:02 |        |
    |   3 |    NESTED LOOPS OUTER                           |                      |     1 |   498 |   100   (1)| 00:00:02 |        |
    |*  4 |     FILTER                                      |                      |       |       |            |          |        |
    |   5 |      NESTED LOOPS OUTER                         |                      |     1 |   474 |    99   (2)| 00:00:02 |        |
    |   6 |       NESTED LOOPS OUTER                        |                      |     1 |   435 |    96   (2)| 00:00:02 |        |
    |   7 |        NESTED LOOPS OUTER                       |                      |     1 |   411 |    95   (2)| 00:00:02 |        |
    |*  8 |         FILTER                                  |                      |       |       |            |          |        |
    |   9 |          NESTED LOOPS OUTER                     |                      |     1 |   387 |    94   (2)| 00:00:02 |        |
    |* 10 |           FILTER                                |                      |       |       |            |          |        |
    |  11 |            NESTED LOOPS OUTER                   |                      |     1 |   348 |    91   (2)| 00:00:02 |        |
    |  12 |             NESTED LOOPS OUTER                  |                      |     1 |   309 |    88   (2)| 00:00:02 |        |
    |  13 |              NESTED LOOPS OUTER                 |                      |     1 |   285 |    87   (2)| 00:00:02 |        |
    |  14 |               NESTED LOOPS OUTER                |                      |     1 |   261 |    86   (2)| 00:00:02 |        |
    |* 15 |                FILTER                           |                      |       |       |            |          |        |
    |  16 |                 NESTED LOOPS OUTER              |                      |     1 |   237 |    85   (2)| 00:00:02 |        |
    |* 17 |                  FILTER                         |                      |       |       |            |          |        |
    |  18 |                   NESTED LOOPS OUTER            |                      |     1 |   198 |    82   (2)| 00:00:01 |        |
    |  19 |                    NESTED LOOPS                 |                      |     1 |   159 |    79   (2)| 00:00:01 |        |
    |  20 |                     NESTED LOOPS                |                      |     1 |   118 |    76   (2)| 00:00:01 |        |
    |  21 |                      MERGE JOIN CARTESIAN       |                      |     1 |    94 |    71   (2)| 00:00:01 |        |
    |* 22 |                       TABLE ACCESS FULL         | TEMP_PART_PRICE_DATE |     1 |    85 |     2   (0)| 00:00:01 |   OR11 |
    |  23 |                       BUFFER SORT               |                      |  7016 | 63144 |    69   (2)| 00:00:01 |        |
    |* 24 |                        TABLE ACCESS FULL        | CUSTOMER_ORDER       |  7016 | 63144 |    69   (2)| 00:00:01 |   OR11 |
    |* 25 |                      TABLE ACCESS BY INDEX ROWID| CUST_ORDER_LINE      |     1 |    24 |     5   (0)| 00:00:01 |   OR11 |
    |* 26 |                       INDEX RANGE SCAN          | SYS_C0028623         |     9 |       |     1   (0)| 00:00:01 |   OR11 |
    |  27 |                     TABLE ACCESS BY INDEX ROWID | REQUIREMENT          |     1 |    41 |     3   (0)| 00:00:01 |   OR11 |
    |* 28 |                      INDEX RANGE SCAN           | X_REQUIREMENT_5      |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  29 |                    TABLE ACCESS BY INDEX ROWID  | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 30 |                     INDEX RANGE SCAN            | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  31 |                  TABLE ACCESS BY INDEX ROWID    | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 32 |                   INDEX RANGE SCAN              | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 33 |                TABLE ACCESS BY INDEX ROWID      | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 34 |                 INDEX UNIQUE SCAN               | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 35 |               TABLE ACCESS BY INDEX ROWID       | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 36 |                INDEX UNIQUE SCAN                | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 37 |              TABLE ACCESS BY INDEX ROWID        | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 38 |               INDEX UNIQUE SCAN                 | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |  39 |             TABLE ACCESS BY INDEX ROWID         | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 40 |              INDEX RANGE SCAN                   | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  41 |           TABLE ACCESS BY INDEX ROWID           | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 42 |            INDEX RANGE SCAN                     | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 43 |         TABLE ACCESS BY INDEX ROWID             | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 44 |          INDEX UNIQUE SCAN                      | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 45 |        TABLE ACCESS BY INDEX ROWID              | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 46 |         INDEX UNIQUE SCAN                       | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |  47 |       TABLE ACCESS BY INDEX ROWID               | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 48 |        INDEX RANGE SCAN                         | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 49 |     TABLE ACCESS BY INDEX ROWID                 | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 50 |      INDEX UNIQUE SCAN                          | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    ---------------------------------------------------------------------------------------------------------------------------------

    Predicate Information (identified by operation id):
    ---------------------------------------------------

       4 - filter(("TPPD"."PART_ID"=DECODE(DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID"
                  ,NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'N
                  ONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)),NULL,NULL,DECODE
                  (NVL("P5"."FABRICATED",'NONE'),'Y',"R6"."PART_ID",NULL)),NULL,DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NO
                  NE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(N
                  VL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",
                  NULL)),NULL,DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."
                  FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NU
                  LL,DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NON
                  E'),'Y',"R3"."PART_ID",NULL)),NULL,DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,"R"."PART_ID"
                  ,DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL)),DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_
                  ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL))),DECODE(DECODE(DECODE(NVL("P"."FABRICA
                  TED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,
                  DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL))),DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),
                  'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P
                  3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)
                  )),DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FA
                  BRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL
                  ,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P5"."FABRICATED",'NONE'),'Y',"
                  R6"."PART_ID",NULL))) AND "R6"."SUBORD_WO_SUB_ID" IS NULL))
       8 - filter("R5"."SUBORD_WO_SUB_ID" IS NULL)
      10 - filter("R4"."SUBORD_WO_SUB_ID" IS NULL)
      15 - filter("R3"."SUBORD_WO_SUB_ID" IS NULL)
      17 - filter("R2"."SUBORD_WO_SUB_ID" IS NULL)
      22 - filter("TPPD"."INCREASE"<>0)
      24 - filter(("CO"."STATUS"='C' OR "CO"."STATUS"='F' OR "CO"."STATUS"='P' OR "CO"."STATUS"='R' OR "CO"."STATUS"='U'))
      25 - filter("COL"."DESIRED_SHIP_DATE">SYSDATE@!-365)
      26 - access("CO"."ID"="COL"."CUST_ORDER_ID")
      28 - access("R"."WORKORDER_TYPE"='M' AND "COL"."PART_ID"="R"."WORKORDER_BASE_ID" AND "R"."WORKORDER_LOT_ID"='0' AND
                  "R"."WORKORDER_SPLIT_ID"='0' AND "R"."SUBORD_WO_SUB_ID" IS NULL)
      30 - access("R2"."WORKORDER_TYPE"='M' AND "R"."PART_ID"="R2"."WORKORDER_BASE_ID" AND "R2"."WORKORDER_LOT_ID"='0')
      32 - access("R3"."WORKORDER_TYPE"='M' AND "R2"."PART_ID"="R3"."WORKORDER_BASE_ID" AND "R3"."WORKORDER_LOT_ID"='0')
      33 - filter("P3"."FABRICATED"='Y')
      34 - access("R3"."PART_ID"="P3"."ID")
      35 - filter("P"."FABRICATED"='Y')
      36 - access("R"."PART_ID"="P"."ID")
      37 - filter("P2"."FABRICATED"='Y')
      38 - access("R2"."PART_ID"="P2"."ID")
      40 - access("R4"."WORKORDER_TYPE"='M' AND "R3"."PART_ID"="R4"."WORKORDER_BASE_ID" AND "R4"."WORKORDER_LOT_ID"='0')
      42 - access("R5"."WORKORDER_TYPE"='M' AND "R4"."PART_ID"="R5"."WORKORDER_BASE_ID" AND "R5"."WORKORDER_LOT_ID"='0')
      43 - filter("P4"."FABRICATED"='Y')
      44 - access("R4"."PART_ID"="P4"."ID")
      45 - filter("P5"."FABRICATED"='Y')
      46 - access("R5"."PART_ID"="P5"."ID")
      48 - access("R6"."WORKORDER_TYPE"='M' AND "R5"."PART_ID"="R6"."WORKORDER_BASE_ID" AND "R6"."WORKORDER_LOT_ID"='0')
      49 - filter("P6"."FABRICATED"='Y')
      50 - access("R6"."PART_ID"="P6"."ID")

  Handle: 2152 User Time: 37.44S Sys  Time: 0S ElapsedTime: 76S Priority: 8 ThreadState: Running
   PID:19 SPID:2152 SID:145 SERIAL#:12 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is the long description for this number '|| TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000)))...

    SQL_ID  6hxcgpp1rd1ag, child number 0
    -------------------------------------
    INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is
    the long description for this number '||
    TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000))) FROM   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000),   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000)

    Plan hash value: 643243249

    -----------------------------------------------------------------------------------
    | Id  | Operation                          | Name | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                   |      |       |     4 (100)|          |
    |   1 |  LOAD TABLE CONVENTIONAL           |      |       |            |          |
    |   2 |   COUNT                            |      |       |            |          |
    |   3 |    MERGE JOIN CARTESIAN            |      |     1 |     4   (0)| 00:00:01 |
    |   4 |     VIEW                           |      |     1 |     2   (0)| 00:00:01 |
    |   5 |      COUNT                         |      |       |            |          |
    |   6 |       CONNECT BY WITHOUT FILTERING |      |       |            |          |
    |   7 |        FAST DUAL                   |      |     1 |     2   (0)| 00:00:01 |
    |   8 |     BUFFER SORT                    |      |     1 |     4   (0)| 00:00:01 |
    |   9 |      VIEW                          |      |     1 |     2   (0)| 00:00:01 |
    |  10 |       COUNT                        |      |       |            |          |
    |  11 |        CONNECT BY WITHOUT FILTERING|      |       |            |          |
    |  12 |         FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------

1/8/2010 12:51:07 PM Processes: 73 Threads: 865 C. Switches: 41992 Q. Length: 7
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 10.81S Sys  Time: 26.83S Memory: 276.09MB Page File: 0.63MB
  Handle: 1444 User Time: 10.02S Sys  Time: 25.51S ElapsedTime: 71S Priority: 8 ThreadState: Running

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 112.71S Sys  Time: 0.09S Memory: 545.63MB Page File: 0.67MB
  Handle: 3520 User Time: 72.57S Sys  Time: 0S ElapsedTime: 74S Priority: 10 ThreadState: Idle
   PID:18 SPID:3520 SID:146 SERIAL#:4 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   SELECT   TOP_LEVEL_PART_ID, REQ.PURC_PART_ID, REQ.TOTAL_QTY,   REQ.TOTAL_QTY*TPPD.INCREASE "Increase",   REQ.R1_PART_ID, REQ.R2_PART_ID, REQ.R3_PART_ID,...

    SQL_ID  3p1v051atxt1z, child number 0
    -------------------------------------
    SELECT   TOP_LEVEL_PART_ID, REQ.PURC_PART_ID, REQ.TOTAL_QTY,  
    REQ.TOTAL_QTY*TPPD.INCREASE "Increase",   REQ.R1_PART_ID,
    REQ.R2_PART_ID, REQ.R3_PART_ID,   REQ.R4_PART_ID, REQ.R5_PART_ID,
    REQ.R6_PART_ID,   REQ.R1_CALC_QTY, REQ.R2_CALC_QTY, REQ.R3_CALC_QTY,  
    REQ.R4_CALC_QTY, REQ.R5_CALC_QTY, REQ.R6_CALC_QTY FROM   (SELECT    
    TOP_LEVEL_PART_ID,     DECODE(R6_PART_ID,NULL,      
    DECODE(R5_PART_ID,NULL,         DECODE(R4_PART_ID,NULL,          
    DECODE(R3_PART_ID,NULL,             DECODE(R2_PART_ID,NULL,            
           R1_PART_ID,R2_PART_ID),                  R3_PART_ID),           
        R4_PART_ID),              R5_PART_ID),            R6_PART_ID)
    PURC_PART_ID,     NVL(R1_CALC_QTY,0)*NVL(R2_CALC_QTY,1)*NVL(R3_CALC_QTY,
    1)*NVL(R4_CALC_QTY,1)*NVL(R5_CALC_QTY,1)*NVL(R6_CALC_QTY,1) TOTAL_QTY, 
       R1_PART_ID, R1_CALC_QTY, R2_PART_ID, R2_CALC_QTY, R3_PART_ID,
    R3_CALC_QTY,     R4_PART_ID, R4_CALC_QTY, R5_PART_ID, R5_CALC_QTY,
    R6_PART_ID, R6_CALC_QTY   FROM     (SELECT       PL.PART_ID TOP_LEVE

    Plan hash value: 3313542492

    ---------------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                                       | Name                 | Rows  | Bytes | Cost (%CPU)| Time     | Inst   |
    ---------------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                                |                      |       |       |   101 (100)|          |        |
    |   1 |  VIEW                                           | VM_NWVW_1            |     1 |   225 |   101   (2)| 00:00:02 |   OR11 |
    |   2 |   HASH UNIQUE                                   |                      |     1 |   498 |   101   (2)| 00:00:02 |        |
    |   3 |    NESTED LOOPS OUTER                           |                      |     1 |   498 |   100   (1)| 00:00:02 |        |
    |*  4 |     FILTER                                      |                      |       |       |            |          |        |
    |   5 |      NESTED LOOPS OUTER                         |                      |     1 |   474 |    99   (2)| 00:00:02 |        |
    |   6 |       NESTED LOOPS OUTER                        |                      |     1 |   435 |    96   (2)| 00:00:02 |        |
    |   7 |        NESTED LOOPS OUTER                       |                      |     1 |   411 |    95   (2)| 00:00:02 |        |
    |*  8 |         FILTER                                  |                      |       |       |            |          |        |
    |   9 |          NESTED LOOPS OUTER                     |                      |     1 |   387 |    94   (2)| 00:00:02 |        |
    |* 10 |           FILTER                                |                      |       |       |            |          |        |
    |  11 |            NESTED LOOPS OUTER                   |                      |     1 |   348 |    91   (2)| 00:00:02 |        |
    |  12 |             NESTED LOOPS OUTER                  |                      |     1 |   309 |    88   (2)| 00:00:02 |        |
    |  13 |              NESTED LOOPS OUTER                 |                      |     1 |   285 |    87   (2)| 00:00:02 |        |
    |  14 |               NESTED LOOPS OUTER                |                      |     1 |   261 |    86   (2)| 00:00:02 |        |
    |* 15 |                FILTER                           |                      |       |       |            |          |        |
    |  16 |                 NESTED LOOPS OUTER              |                      |     1 |   237 |    85   (2)| 00:00:02 |        |
    |* 17 |                  FILTER                         |                      |       |       |            |          |        |
    |  18 |                   NESTED LOOPS OUTER            |                      |     1 |   198 |    82   (2)| 00:00:01 |        |
    |  19 |                    NESTED LOOPS                 |                      |     1 |   159 |    79   (2)| 00:00:01 |        |
    |  20 |                     NESTED LOOPS                |                      |     1 |   118 |    76   (2)| 00:00:01 |        |
    |  21 |                      MERGE JOIN CARTESIAN       |                      |     1 |    94 |    71   (2)| 00:00:01 |        |
    |* 22 |                       TABLE ACCESS FULL         | TEMP_PART_PRICE_DATE |     1 |    85 |     2   (0)| 00:00:01 |   OR11 |
    |  23 |                       BUFFER SORT               |                      |  7016 | 63144 |    69   (2)| 00:00:01 |        |
    |* 24 |                        TABLE ACCESS FULL        | CUSTOMER_ORDER       |  7016 | 63144 |    69   (2)| 00:00:01 |   OR11 |
    |* 25 |                      TABLE ACCESS BY INDEX ROWID| CUST_ORDER_LINE      |     1 |    24 |     5   (0)| 00:00:01 |   OR11 |
    |* 26 |                       INDEX RANGE SCAN          | SYS_C0028623         |     9 |       |     1   (0)| 00:00:01 |   OR11 |
    |  27 |                     TABLE ACCESS BY INDEX ROWID | REQUIREMENT          |     1 |    41 |     3   (0)| 00:00:01 |   OR11 |
    |* 28 |                      INDEX RANGE SCAN           | X_REQUIREMENT_5      |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  29 |                    TABLE ACCESS BY INDEX ROWID  | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 30 |                     INDEX RANGE SCAN            | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  31 |                  TABLE ACCESS BY INDEX ROWID    | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 32 |                   INDEX RANGE SCAN              | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 33 |                TABLE ACCESS BY INDEX ROWID      | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 34 |                 INDEX UNIQUE SCAN               | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 35 |               TABLE ACCESS BY INDEX ROWID       | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 36 |                INDEX UNIQUE SCAN                | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 37 |              TABLE ACCESS BY INDEX ROWID        | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 38 |               INDEX UNIQUE SCAN                 | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |  39 |             TABLE ACCESS BY INDEX ROWID         | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 40 |              INDEX RANGE SCAN                   | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |  41 |           TABLE ACCESS BY INDEX ROWID           | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 42 |            INDEX RANGE SCAN                     | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 43 |         TABLE ACCESS BY INDEX ROWID             | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 44 |          INDEX UNIQUE SCAN                      | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |* 45 |        TABLE ACCESS BY INDEX ROWID              | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 46 |         INDEX UNIQUE SCAN                       | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    |  47 |       TABLE ACCESS BY INDEX ROWID               | REQUIREMENT          |     1 |    39 |     3   (0)| 00:00:01 |   OR11 |
    |* 48 |        INDEX RANGE SCAN                         | SYS_C0028831         |     1 |       |     2   (0)| 00:00:01 |   OR11 |
    |* 49 |     TABLE ACCESS BY INDEX ROWID                 | PART                 |     1 |    24 |     1   (0)| 00:00:01 |   OR11 |
    |* 50 |      INDEX UNIQUE SCAN                          | SYS_C0028742         |     1 |       |     0   (0)|          |   OR11 |
    ---------------------------------------------------------------------------------------------------------------------------------

    Predicate Information (identified by operation id):
    ---------------------------------------------------

       4 - filter(("TPPD"."PART_ID"=DECODE(DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID"
                  ,NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'N
                  ONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)),NULL,NULL,DECODE
                  (NVL("P5"."FABRICATED",'NONE'),'Y',"R6"."PART_ID",NULL)),NULL,DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NO
                  NE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(N
                  VL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",
                  NULL)),NULL,DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."
                  FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NU
                  LL,DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NON
                  E'),'Y',"R3"."PART_ID",NULL)),NULL,DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,"R"."PART_ID"
                  ,DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL)),DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_
                  ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL))),DECODE(DECODE(DECODE(NVL("P"."FABRICA
                  TED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,
                  DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL))),DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),
                  'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FABRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P
                  3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)
                  )),DECODE(DECODE(DECODE(DECODE(DECODE(NVL("P"."FABRICATED",'NONE'),'Y',"R2"."PART_ID",NULL),NULL,NULL,DECODE(NVL("P2"."FA
                  BRICATED",'NONE'),'Y',"R3"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P3"."FABRICATED",'NONE'),'Y',"R4"."PART_ID",NULL)),NULL
                  ,NULL,DECODE(NVL("P4"."FABRICATED",'NONE'),'Y',"R5"."PART_ID",NULL)),NULL,NULL,DECODE(NVL("P5"."FABRICATED",'NONE'),'Y',"
                  R6"."PART_ID",NULL))) AND "R6"."SUBORD_WO_SUB_ID" IS NULL))
       8 - filter("R5"."SUBORD_WO_SUB_ID" IS NULL)
      10 - filter("R4"."SUBORD_WO_SUB_ID" IS NULL)
      15 - filter("R3"."SUBORD_WO_SUB_ID" IS NULL)
      17 - filter("R2"."SUBORD_WO_SUB_ID" IS NULL)
      22 - filter("TPPD"."INCREASE"<>0)
      24 - filter(("CO"."STATUS"='C' OR "CO"."STATUS"='F' OR "CO"."STATUS"='P' OR "CO"."STATUS"='R' OR "CO"."STATUS"='U'))
      25 - filter("COL"."DESIRED_SHIP_DATE">SYSDATE@!-365)
      26 - access("CO"."ID"="COL"."CUST_ORDER_ID")
      28 - access("R"."WORKORDER_TYPE"='M' AND "COL"."PART_ID"="R"."WORKORDER_BASE_ID" AND "R"."WORKORDER_LOT_ID"='0' AND
                  "R"."WORKORDER_SPLIT_ID"='0' AND "R"."SUBORD_WO_SUB_ID" IS NULL)
      30 - access("R2"."WORKORDER_TYPE"='M' AND "R"."PART_ID"="R2"."WORKORDER_BASE_ID" AND "R2"."WORKORDER_LOT_ID"='0')
      32 - access("R3"."WORKORDER_TYPE"='M' AND "R2"."PART_ID"="R3"."WORKORDER_BASE_ID" AND "R3"."WORKORDER_LOT_ID"='0')
      33 - filter("P3"."FABRICATED"='Y')
      34 - access("R3"."PART_ID"="P3"."ID")
      35 - filter("P"."FABRICATED"='Y')
      36 - access("R"."PART_ID"="P"."ID")
      37 - filter("P2"."FABRICATED"='Y')
      38 - access("R2"."PART_ID"="P2"."ID")
      40 - access("R4"."WORKORDER_TYPE"='M' AND "R3"."PART_ID"="R4"."WORKORDER_BASE_ID" AND "R4"."WORKORDER_LOT_ID"='0')
      42 - access("R5"."WORKORDER_TYPE"='M' AND "R4"."PART_ID"="R5"."WORKORDER_BASE_ID" AND "R5"."WORKORDER_LOT_ID"='0')
      43 - filter("P4"."FABRICATED"='Y')
      44 - access("R4"."PART_ID"="P4"."ID")
      45 - filter("P5"."FABRICATED"='Y')
      46 - access("R5"."PART_ID"="P5"."ID")
      48 - access("R6"."WORKORDER_TYPE"='M' AND "R5"."PART_ID"="R6"."WORKORDER_BASE_ID" AND "R6"."WORKORDER_LOT_ID"='0')
      49 - filter("P6"."FABRICATED"='Y')
      50 - access("R6"."PART_ID"="P6"."ID")

  Handle: 2152 User Time: 37.07S Sys  Time: 0.05S ElapsedTime: 75S Priority: 8 ThreadState: In Run Queue
   PID:19 SPID:2152 SID:145 SERIAL#:12 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is the long description for this number '|| TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000)))...

    SQL_ID  6hxcgpp1rd1ag, child number 0
    -------------------------------------
    INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is
    the long description for this number '||
    TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000))) FROM   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000),   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000)

    Plan hash value: 643243249

    -----------------------------------------------------------------------------------
    | Id  | Operation                          | Name | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                   |      |       |     4 (100)|          |
    |   1 |  LOAD TABLE CONVENTIONAL           |      |       |            |          |
    |   2 |   COUNT                            |      |       |            |          |
    |   3 |    MERGE JOIN CARTESIAN            |      |     1 |     4   (0)| 00:00:01 |
    |   4 |     VIEW                           |      |     1 |     2   (0)| 00:00:01 |
    |   5 |      COUNT                         |      |       |            |          |
    |   6 |       CONNECT BY WITHOUT FILTERING |      |       |            |          |
    |   7 |        FAST DUAL                   |      |     1 |     2   (0)| 00:00:01 |
    |   8 |     BUFFER SORT                    |      |     1 |     4   (0)| 00:00:01 |
    |   9 |      VIEW                          |      |     1 |     2   (0)| 00:00:01 |
    |  10 |       COUNT                        |      |       |            |          |
    |  11 |        CONNECT BY WITHOUT FILTERING|      |       |            |          |
    |  12 |         FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------

1/8/2010 12:52:17 PM Processes: 73 Threads: 861 C. Switches: 1548363 Q. Length: 5
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 11.42S Sys  Time: 27.27S Memory: 232MB Page File: 0.63MB
  Handle: 1444 User Time: 11.85S Sys  Time: 27.97S ElapsedTime: 69S Priority: 8 ThreadState: Running

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 55.19S Sys  Time: 0.78S Memory: 519.81MB Page File: 0.67MB
  Handle: 2152 User Time: 48.79S Sys  Time: 0.66S ElapsedTime: 67S Priority: 8 ThreadState: In Run Queue
   PID:19 SPID:2152 SID:145 SERIAL#:12 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is the long description for this number '|| TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000)))...

    SQL_ID  6hxcgpp1rd1ag, child number 0
    -------------------------------------
    INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is
    the long description for this number '||
    TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000))) FROM   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000),   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000)

    Plan hash value: 643243249

    -----------------------------------------------------------------------------------
    | Id  | Operation                          | Name | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                   |      |       |     4 (100)|          |
    |   1 |  LOAD TABLE CONVENTIONAL           |      |       |            |          |
    |   2 |   COUNT                            |      |       |            |          |
    |   3 |    MERGE JOIN CARTESIAN            |      |     1 |     4   (0)| 00:00:01 |
    |   4 |     VIEW                           |      |     1 |     2   (0)| 00:00:01 |
    |   5 |      COUNT                         |      |       |            |          |
    |   6 |       CONNECT BY WITHOUT FILTERING |      |       |            |          |
    |   7 |        FAST DUAL                   |      |     1 |     2   (0)| 00:00:01 |
    |   8 |     BUFFER SORT                    |      |     1 |     4   (0)| 00:00:01 |
    |   9 |      VIEW                          |      |     1 |     2   (0)| 00:00:01 |
    |  10 |       COUNT                        |      |       |            |          |
    |  11 |        CONNECT BY WITHOUT FILTERING|      |       |            |          |
    |  12 |         FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------

1/8/2010 12:53:24 PM Processes: 73 Threads: 859 C. Switches: 66962 Q. Length: 2
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 21.67S Sys  Time: 43.91S Memory: 175.28MB Page File: 0.63MB
  Handle: 1444 User Time: 22.11S Sys  Time: 45.18S ElapsedTime: 70S Priority: 8 ThreadState: Running

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 65.88S Sys  Time: 0.08S Memory: 441.9MB Page File: 0.67MB
  Handle: 2152 User Time: 66.77S Sys  Time: 0.06S ElapsedTime: 68S Priority: 8 ThreadState: Running
   PID:19 SPID:2152 SID:145 SERIAL#:12 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is the long description for this number '|| TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000)))...

    SQL_ID  6hxcgpp1rd1ag, child number 0
    -------------------------------------
    INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is
    the long description for this number '||
    TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000))) FROM   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000),   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000)

    Plan hash value: 643243249

    -----------------------------------------------------------------------------------
    | Id  | Operation                          | Name | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                   |      |       |     4 (100)|          |
    |   1 |  LOAD TABLE CONVENTIONAL           |      |       |            |          |
    |   2 |   COUNT                            |      |       |            |          |
    |   3 |    MERGE JOIN CARTESIAN            |      |     1 |     4   (0)| 00:00:01 |
    |   4 |     VIEW                           |      |     1 |     2   (0)| 00:00:01 |
    |   5 |      COUNT                         |      |       |            |          |
    |   6 |       CONNECT BY WITHOUT FILTERING |      |       |            |          |
    |   7 |        FAST DUAL                   |      |     1 |     2   (0)| 00:00:01 |
    |   8 |     BUFFER SORT                    |      |     1 |     4   (0)| 00:00:01 |
    |   9 |      VIEW                          |      |     1 |     2   (0)| 00:00:01 |
    |  10 |       COUNT                        |      |       |            |          |
    |  11 |        CONNECT BY WITHOUT FILTERING|      |       |            |          |
    |  12 |         FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------

1/8/2010 12:54:35 PM Processes: 73 Threads: 860 C. Switches: 47967 Q. Length: 5
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 15.63S Sys  Time: 48.93S Memory: 141.9MB Page File: 0.63MB
  Handle: 1444 User Time: 14.4S Sys  Time: 46.38S ElapsedTime: 68S Priority: 8 ThreadState: In Run Queue

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 75.85S Sys  Time: 0.09S Memory: 438.77MB Page File: 0.67MB
  Handle: 2152 User Time: 64.79S Sys  Time: 0S ElapsedTime: 71S Priority: 8 ThreadState: In Run Queue
   PID:19 SPID:2152 SID:145 SERIAL#:12 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is the long description for this number '|| TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000)))...

    SQL_ID  6hxcgpp1rd1ag, child number 0
    -------------------------------------
    INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is
    the long description for this number '||
    TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000))) FROM   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000),   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000)

    Plan hash value: 643243249

    -----------------------------------------------------------------------------------
    | Id  | Operation                          | Name | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                   |      |       |     4 (100)|          |
    |   1 |  LOAD TABLE CONVENTIONAL           |      |       |            |          |
    |   2 |   COUNT                            |      |       |            |          |
    |   3 |    MERGE JOIN CARTESIAN            |      |     1 |     4   (0)| 00:00:01 |
    |   4 |     VIEW                           |      |     1 |     2   (0)| 00:00:01 |
    |   5 |      COUNT                         |      |       |            |          |
    |   6 |       CONNECT BY WITHOUT FILTERING |      |       |            |          |
    |   7 |        FAST DUAL                   |      |     1 |     2   (0)| 00:00:01 |
    |   8 |     BUFFER SORT                    |      |     1 |     4   (0)| 00:00:01 |
    |   9 |      VIEW                          |      |     1 |     2   (0)| 00:00:01 |
    |  10 |       COUNT                        |      |       |            |          |
    |  11 |        CONNECT BY WITHOUT FILTERING|      |       |            |          |
    |  12 |         FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------

1/8/2010 12:55:42 PM Processes: 73 Threads: 864 C. Switches: 35978 Q. Length: 6
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 8.83S Sys  Time: 23.93S Memory: 141.25MB Page File: 0.63MB
  Handle: 1444 User Time: 9.08S Sys  Time: 24.16S ElapsedTime: 68S Priority: 8 ThreadState: In Run Queue

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 98.7S Sys  Time: 0.22S Memory: 445.1MB Page File: 0.67MB
  Handle: 2152 User Time: 32.07S Sys  Time: 0S ElapsedTime: 65S Priority: 8 ThreadState: In Run Queue
   PID:19 SPID:2152 SID:145 SERIAL#:12 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is the long description for this number '|| TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000)))...

    SQL_ID  6hxcgpp1rd1ag, child number 0
    -------------------------------------
    INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is
    the long description for this number '||
    TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000))) FROM   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000),   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000)

    Plan hash value: 643243249

    -----------------------------------------------------------------------------------
    | Id  | Operation                          | Name | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                   |      |       |     4 (100)|          |
    |   1 |  LOAD TABLE CONVENTIONAL           |      |       |            |          |
    |   2 |   COUNT                            |      |       |            |          |
    |   3 |    MERGE JOIN CARTESIAN            |      |     1 |     4   (0)| 00:00:01 |
    |   4 |     VIEW                           |      |     1 |     2   (0)| 00:00:01 |
    |   5 |      COUNT                         |      |       |            |          |
    |   6 |       CONNECT BY WITHOUT FILTERING |      |       |            |          |
    |   7 |        FAST DUAL                   |      |     1 |     2   (0)| 00:00:01 |
    |   8 |     BUFFER SORT                    |      |     1 |     4   (0)| 00:00:01 |
    |   9 |      VIEW                          |      |     1 |     2   (0)| 00:00:01 |
    |  10 |       COUNT                        |      |       |            |          |
    |  11 |        CONNECT BY WITHOUT FILTERING|      |       |            |          |
    |  12 |         FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------

  Handle: 5960 User Time: 65.07S Sys  Time: 0S ElapsedTime: 65S Priority: 8 ThreadState: Running
   PID:20 SPID:5960 SID:132 SERIAL#:55 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   DECLARE   i NUMBER := 0;   STime DATE := SYSDATE; BEGIN   WHILE (SYSDATE - STime) < 0.006945 LOOP     i := i + + 0.000001;   End Loop; End;

    SQL_ID  cujkdbu2npk0x, child number 0

    DECLARE   i NUMBER := 0;   STime DATE := SYSDATE; BEGIN   WHILE
    (SYSDATE - STime) < 0.006945 LOOP     i := i + + 0.000001;   End Loop;
    End;

    NOTE: cannot fetch plan for SQL_ID: cujkdbu2npk0x, CHILD_NUMBER: 0
          Please verify value of SQL_ID and CHILD_NUMBER;
          It could also be that the plan is no longer in cursor cache (check v$sql_plan)

1/8/2010 12:56:47 PM Processes: 73 Threads: 856 C. Switches: 59846 Q. Length: 5
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 9.3S Sys  Time: 22.7S Memory: 141.1MB Page File: 0.63MB
  Handle: 1444 User Time: 9.25S Sys  Time: 22.85S ElapsedTime: 65S Priority: 8 ThreadState: In Run Queue

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 96.47S Sys  Time: 0.03S Memory: 440.21MB Page File: 0.67MB
  Handle: 2152 User Time: 32.72S Sys  Time: 0S ElapsedTime: 67S Priority: 8 ThreadState: In Run Queue
   PID:19 SPID:2152 SID:145 SERIAL#:12 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is the long description for this number '|| TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000)))...

    SQL_ID  6hxcgpp1rd1ag, child number 0
    -------------------------------------
    INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is
    the long description for this number '||
    TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000))) FROM   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000),   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000)

    Plan hash value: 643243249

    -----------------------------------------------------------------------------------
    | Id  | Operation                          | Name | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                   |      |       |     4 (100)|          |
    |   1 |  LOAD TABLE CONVENTIONAL           |      |       |            |          |
    |   2 |   COUNT                            |      |       |            |          |
    |   3 |    MERGE JOIN CARTESIAN            |      |     1 |     4   (0)| 00:00:01 |
    |   4 |     VIEW                           |      |     1 |     2   (0)| 00:00:01 |
    |   5 |      COUNT                         |      |       |            |          |
    |   6 |       CONNECT BY WITHOUT FILTERING |      |       |            |          |
    |   7 |        FAST DUAL                   |      |     1 |     2   (0)| 00:00:01 |
    |   8 |     BUFFER SORT                    |      |     1 |     4   (0)| 00:00:01 |
    |   9 |      VIEW                          |      |     1 |     2   (0)| 00:00:01 |
    |  10 |       COUNT                        |      |       |            |          |
    |  11 |        CONNECT BY WITHOUT FILTERING|      |       |            |          |
    |  12 |         FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------

  Handle: 5960 User Time: 54.01S Sys  Time: 0S ElapsedTime: 67S Priority: 8 ThreadState: In Run Queue
   PID:20 SPID:5960 SID:132 SERIAL#:55 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   DECLARE   i NUMBER := 0;   STime DATE := SYSDATE; BEGIN   WHILE (SYSDATE - STime) < 0.006945 LOOP     i := i + + 0.000001;   End Loop; End;

    SQL_ID  cujkdbu2npk0x, child number 0

    DECLARE   i NUMBER := 0;   STime DATE := SYSDATE; BEGIN   WHILE
    (SYSDATE - STime) < 0.006945 LOOP     i := i + + 0.000001;   End Loop;
    End;

    NOTE: cannot fetch plan for SQL_ID: cujkdbu2npk0x, CHILD_NUMBER: 0
          Please verify value of SQL_ID and CHILD_NUMBER;
          It could also be that the plan is no longer in cursor cache (check v$sql_plan)

1/8/2010 12:57:56 PM Processes: 73 Threads: 861 C. Switches: 35972 Q. Length: 6
Instance: c:\oracle\product\10.2.0\db_1\bin\ORACLE.EXE OR10
 User Time: 9.33S Sys  Time: 25.19S Memory: 141.11MB Page File: 0.63MB
  Handle: 1444 User Time: 9.34S Sys  Time: 25.5S ElapsedTime: 70S Priority: 8 ThreadState: In Run Queue

Instance: c:\oracle\product\11.1.0\db_1\bin\ORACLE.EXE OR11
 User Time: 103.9S Sys  Time: 0.13S Memory: 442.42MB Page File: 0.67MB
  Handle: 3520 User Time: 34.13S Sys  Time: 0.02S ElapsedTime: 69S Priority: 8 ThreadState: Running
   PID:18 SPID:3520 SID:146 SERIAL#:4 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   DECLARE   i NUMBER := 0;   STime DATE := SYSDATE; BEGIN   WHILE (SYSDATE - STime) < 0.006945 LOOP     i := i + + 0.000001;   End Loop; End;

    SQL_ID  cujkdbu2npk0x, child number 0

    DECLARE   i NUMBER := 0;   STime DATE := SYSDATE; BEGIN   WHILE
    (SYSDATE - STime) < 0.006945 LOOP     i := i + + 0.000001;   End Loop;
    End;

    NOTE: cannot fetch plan for SQL_ID: cujkdbu2npk0x, CHILD_NUMBER: 0
          Please verify value of SQL_ID and CHILD_NUMBER;
          It could also be that the plan is no longer in cursor cache (check v$sql_plan)

  Handle: 2152 User Time: 34.04S Sys  Time: 0S ElapsedTime: 69S Priority: 8 ThreadState: In Run Queue
   PID:19 SPID:2152 SID:145 SERIAL#:12 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is the long description for this number '|| TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000)))...

    SQL_ID  6hxcgpp1rd1ag, child number 0
    -------------------------------------
    INSERT INTO T1 SELECT   CEIL(ABS(SIN(ROWNUM/9.9999)*10000)),   'This is
    the long description for this number '||
    TO_CHAR(CEIL(ABS(SIN(ROWNUM/9.9999)*10000))) FROM   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000),   (SELECT     ROWNUM
    RN   FROM     DUAL   CONNECT BY     LEVEL<=10000)

    Plan hash value: 643243249

    -----------------------------------------------------------------------------------
    | Id  | Operation                          | Name | Rows  | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                   |      |       |     4 (100)|          |
    |   1 |  LOAD TABLE CONVENTIONAL           |      |       |            |          |
    |   2 |   COUNT                            |      |       |            |          |
    |   3 |    MERGE JOIN CARTESIAN            |      |     1 |     4   (0)| 00:00:01 |
    |   4 |     VIEW                           |      |     1 |     2   (0)| 00:00:01 |
    |   5 |      COUNT                         |      |       |            |          |
    |   6 |       CONNECT BY WITHOUT FILTERING |      |       |            |          |
    |   7 |        FAST DUAL                   |      |     1 |     2   (0)| 00:00:01 |
    |   8 |     BUFFER SORT                    |      |     1 |     4   (0)| 00:00:01 |
    |   9 |      VIEW                          |      |     1 |     2   (0)| 00:00:01 |
    |  10 |       COUNT                        |      |       |            |          |
    |  11 |        CONNECT BY WITHOUT FILTERING|      |       |            |          |
    |  12 |         FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------

  Handle: 5960 User Time: 34.18S Sys  Time: 0S ElapsedTime: 68S Priority: 8 ThreadState: In Run Queue
   PID:20 SPID:5960 SID:132 SERIAL#:55 USERNAME:TESTUSER MACHINE:HOME.NET\AIRFORCE-3 PROGRAM:sqlplus.exe
   DECLARE   i NUMBER := 0;   STime DATE := SYSDATE; BEGIN   WHILE (SYSDATE - STime) < 0.006945 LOOP     i := i + + 0.000001;   End Loop; End;
    SQL_ID  cujkdbu2npk0x, child number 0

    DECLARE   i NUMBER := 0;   STime DATE := SYSDATE; BEGIN   WHILE
    (SYSDATE - STime) < 0.006945 LOOP     i := i + + 0.000001;   End Loop;
    End;

    NOTE: cannot fetch plan for SQL_ID: cujkdbu2npk0x, CHILD_NUMBER: 0
          Please verify value of SQL_ID and CHILD_NUMBER;
          It could also be that the plan is no longer in cursor cache (check v$sql_plan)

Notice in the above that toward the end two sessions were doing nothing but burning CPU in a PL/SQL loop – the script caught the person trying to soak the computer’s dual core CPU.  The CPU queue length jumped significantly as a result of the activity of the two sessions.

Well, the above is neat, but what about the script to create the output?  Sorry, I ran out of space in this blog post – check back in a little while… (Forward to the Follow-Up Article)





The OakTable Network Invades Michigan, USA (Advert)

8 01 2010

January 8, 2009 (Updated February 5)

… at MOS… uh… in light of the Flashing incident with the other MOS, maybe it is better to stick with the abbreviation MOTS – the Michigan OakTable Symposium. 

For the first time ever, the OakTable Network is organizing a two day training event on the Thursday and Friday (September 16th and 17th, 2010) just before Oracle OpenWorld, in Ann Arbor, Michigan (not far from the University of Michigan). The details are still being finalized, so for right now here are the basics:

  • Seating will be limited to a maximum of 300 people.
  • OakTable Network members from around the world will attend and provide the training sessions.
  • Sessions will be provided that will appeal to DBAs and developers.
  • MOTS is not intended as a money making event. As such, the cost for attending the event has been set as low as possible to essentially “break-even”.

Probable Speakers Include (subject to change):

Early registration at a rate of $450 per person will end April 30, 2010. Registration between May 1 and July 31 is $600 per person, and increases to $750 per person after July 31. These prices do not include room hotel costs (roughly $100 per night), but will include a high quality buffet lunch.

More details will follow later.

———-

February 5 Update:

I heard that a  couple of more people will likely be added to the list of presenters, and it looks like I might have the opportunity to present with Randolf.  Biographies for the presenters will hopefully be available within a couple of weeks, along with the site for registration.

—————————————————-

Latest information: Michigan OakTable Symposium (Advert)
Official website for the event: http://michigan.oaktable.net





Deadlock on Oracle 11g but Not on 10g

7 01 2010

January 7, 2010

Is it possible to trigger a deadlock on Oracle Database 11.1.0.6, 11.1.0.7, and 11.2.0.1 when the exact same procedure does not trigger a deadlock on Oracle Database 10.2.0.4?  Trace files containing deadlocks always contain the following advice:

The following deadlock is not an ORACLE error. It is a deadlock due to user error in the design of an application or from issuing incorrect ad-hoc SQL.

In short, the above states that deadlocks are programming and/or end-user errors.

A couple of months after I generated a test case like the one below, I read an interesting blog article by Mark Bobak, a fellow OakTable Network member and frequent contributor to the Oracle OTN forums and Oracle-L list.  I then started to wonder if the test case I put together applied to Mark’s article (clarification: my test case was built roughly a year after Mark’s article was written, I only recently found Mark’s article).

I have a few favorite Oracle books, and I thought that I would quote from two of those books:

Expert Oracle Database Architecture: 9i and 10g Programming Techniques and Solutions

“So, when do you not need to index a foreign key? The answer is, in general, when the following conditions are met:

  • You do not delete from the parent table.
  • You do not update the parent table’s unique/primary key value (watch for unintended updates to the primary key by tools!).
  • You do not join from the parent to the child (like DEPT to EMP).”

Page 302 of the book “Expert One-On-One” and the reprint Expert Oracle, Signature Edition state essentially the same thing as the above book.

Oracle Wait Interface: a Practical Guide to Performance Diagnostics & Tuning

“Unindexed foreign keys are no longer an issue starting in Oracle9i Database.”

Interesting…

I have not read the following book, but found it through a Google search.  This book seems to take the middle ground:

OCA Oracle Database 11g: Administration I Exam Guide

“However, you should always create indexes on the foreign key columns within the child table for performance reasons: a DELETE on the parent table will be much faster if Oracle can use an index to determine whether there are any rows in the child table referencing the row that is being deleted.” 

OK, combining the three quotes – in general, there are only 3 cases when foreign key columns do not need to be indexed, as of Oracle 9i it is no longer necessary to index foreign key columns, but in Oracle 11g creating an index on a foreign key column will speed up deletes on the parent table.

Let’s keep the above in mind while we trigger a deadlock in Oracle 11.1.0.7. Refer back to the articles on Enqueues and Deadlocks to see if you are able to determine what happens in the test case (or more accurately WHY it happens) (clarification: this test case simulates a “screen painter” type program that updates every column in a row based on the fields on screen – the same behavior should be present if the primary key value in the parent table is updated to the same value, rather than a different value – it also relies on the fact that a row that is inserted by one session but not committed is not visible to another session until the first session commits).

First, we need a couple test tables:

CREATE TABLE T1(
  C1 NUMBER(10) PRIMARY KEY);

INSERT INTO
  T1
SELECT
  ROWNUM
FROM
  DUAL
CONNECT BY
  LEVEL<=100000;

COMMIT;

CREATE TABLE T2(
  C1 NUMBER(10) PRIMARY KEY,
  C2 NUMBER(10),
  CONSTRAINT FK_T1_C1 FOREIGN KEY(C2) REFERENCES T1(C1) ENABLE);

INSERT INTO
  T2
SELECT
  ROWNUM,
  ROWNUM
FROM
  DUAL
CONNECT BY
  LEVEL<=100000;

COMMIT;

GRANT ALL ON T1 TO PUBLIC;
GRANT ALL ON T2 TO PUBLIC;

CREATE PUBLIC SYNONYM T1 FOR T1;
CREATE PUBLIC SYNONYM T2 FOR T2;

OK, we have two tables with a foreign key relationship, and no index on the foreign key column.  Now, we need four sessions:

  • Session 1 connected as TESTUSER (the owner of the tables)
  • Session 2 connected as USER2
  • Session 3 connected as USER3
  • Session 4 connected as SYS (only to query the various performance views)

Session 1:

INSERT INTO T1 VALUES(100010);
INSERT INTO T2 VALUES(100010,100010);

Session 2:

INSERT INTO T1 VALUES(100020);
INSERT INTO T2 VALUES(100020,100020);
DELETE FROM T2 WHERE C1=50;
UPDATE T1 SET C1=100030 WHERE C1=50;

{session 2 hangs}

Session 3:

INSERT INTO T1 VALUES(100020);

{session 3 hangs}

Session 4:

SELECT /*+ ORDERED */
  S.SID,
  S.USERNAME,
  S.PROGRAM,
  S.STATUS,
  SW.EVENT,
  SW.WAIT_TIME WT,
  SW.STATE,
  SW.SECONDS_IN_WAIT S_I_W,
  S.SQL_ID,
  S.SQL_ADDRESS,
  S.SQL_HASH_VALUE,
  S.SQL_CHILD_NUMBER,
  S.ROW_WAIT_OBJ# OBJ#,
  S.ROW_WAIT_FILE# FILE#,
  S.ROW_WAIT_BLOCK# BLOCK#,
  S.ROW_WAIT_ROW# ROW#,
  SW.P1,
  SW.P2,
  SW.P3
FROM
  V$SESSION_WAIT SW,
  V$SESSION S
WHERE
  S.USERNAME IS NOT NULL
  AND SW.SID=S.SID
  AND SW.EVENT NOT LIKE '%SQL*Net%'
  AND SW.EVENT NOT IN ('Streams AQ: waiting for messages in the queue', 'wait for unread message on broadcast channel');

SID USERNAME PROGRAM     STATUS EVENT                WT STATE   S_I_W SQL_ID        SQL_ADDR SQL_HV     CN OBJ# FILE# BLOCK# ROW#         P1    P2 P3
--- -------- ----------- ------ -------------------- -- ------- ----- ------------- -------- ---------- -- ---- ----- ------ ---- ---------- ----- --
307 USER2    sqlplus.exe ACTIVE enq: TM - contention  0 WAITING   422 4rtg0hv0atfkx 224E2B7C 3232545373  0   -1     0      0    0 1414332421 82913  0
314 USER3    sqlplus.exe ACTIVE enq: TM - contention  0 WAITING   407 cv338j6z2530g 224DAE38 3189935119  0   -1     0      0    0 1414332419 82913  0

 

SELECT
  S.SID,
  S.USERNAME,
  S.PROGRAM,
  S.SQL_ID,
  S.SQL_ADDRESS,
  S.SQL_HASH_VALUE,
  S.SQL_CHILD_NUMBER CN,
  S.ROW_WAIT_OBJ#,
  S.ROW_WAIT_FILE#,
  S.ROW_WAIT_BLOCK#,
  S.ROW_WAIT_ROW#,
  L.LMODE,
  L.REQUEST,
  L.ID1,
  L.ID2,
  L.TYPE,
  L.BLOCK
FROM
  V$LOCK L,
  V$SESSION S
WHERE
  (L.ID1, L.ID2, L.TYPE) IN
    (SELECT
      ID1,
      ID2,
      TYPE
    FROM
      V$LOCK
    WHERE
      REQUEST > 0)
  AND L.SID=S.SID;

SID USERNAME PROGRAM     SQL_ID        SQL_ADDR SQL_HASH_VALUE CN ROW_WAIT_OBJ# ROW_WAIT_FILE# ROW_WAIT_BLOCK# ROW_WAIT_ROW# LMODE REQUEST ID1   ID2 TY BLOCK
--- -------- ----------- ------------- -------- -------------- -- ------------- -------------- --------------- ------------- ----- ------- ----- --- -- -----
307 USER2    sqlplus.exe 4rtg0hv0atfkx 224E2B7C     3232545373  0            -1              0               0             0     3       5 82913   0 TM     1
314 USER3    sqlplus.exe cv338j6z2530g 224DAE38     3189935119  0            -1              0               0             0     0       3 82913   0 TM     0
320 TESTUSER sqlplus.exe 0vbusv12hnbk6 22480E10     1158295110  0         12517              1           29656             0     3       0 82913   0 TM     1

 

Session 1:

COMMIT;

Session 3 shows the following:

INSERT INTO T1 VALUES(100020)
            *
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource

Session 2 shows the following:

1 row updated.

The trace file contains the following deadlock information:

[Transaction Deadlock]

The following deadlock is not an ORACLE error. It is a
deadlock due to user error in the design of an application
or from issuing incorrect ad-hoc SQL. The following
information may aid in determining the deadlock:

Deadlock graph:
 ---------Blocker(s)--------  ---------Waiter(s)---------
Resource Name          process session holds waits  process session holds waits
TM-000143e1-00000000        21     314    SX             23     307    SX   SSX
TX-0009000d-000030d3        23     307     X             21     314           S

session 314: DID 0001-0015-00000006 session 307: DID 0001-0017-00000001
session 307: DID 0001-0017-00000001 session 314: DID 0001-0015-00000006

Rows waited on:
  Session 314: no row
  Session 307: obj - rowid = 000143E0 - AAAUPgAAFAAFDpsAAA
  (dictionary objn - 82912, file - 5, block - 1325676, slot - 0)

----- Information for the OTHER waiting sessions -----
Session 307:
  sid: 307 ser: 2 audsid: 1210519 user: 186/USER2 flags: 0x100045
  pid: 23 O/S info: user: SYSTEM, term: TESTBOX, ospid: 940
    image: ORACLE.EXE (SHAD)
  client details:
    O/S info: user: TEST, term: TESTBOX, ospid: 1616:1264
    machine: TESTBOX program: sqlplus.exe
    application name: SQL*Plus, hash value=3669949024
  current SQL:

UPDATE T1 SET C1=100030 WHERE C1=50  

Interesting deadlock.  Now, let’s try the same test with Oracle 10.2.0.4:

The table definitions:

CREATE TABLE T1(
  C1 NUMBER(10) PRIMARY KEY);

INSERT INTO
  T1
SELECT
  ROWNUM
FROM
  DUAL
CONNECT BY
  LEVEL<=100000;

COMMIT;

CREATE TABLE T2(
  C1 NUMBER(10) PRIMARY KEY,
  C2 NUMBER(10),
  CONSTRAINT FK_T1_C1 FOREIGN KEY(C2) REFERENCES T1(C1) ENABLE);

INSERT INTO
  T2
SELECT
  ROWNUM,
  ROWNUM
FROM
  DUAL
CONNECT BY
  LEVEL<=100000;

COMMIT;

GRANT ALL ON T1 TO PUBLIC;
GRANT ALL ON T2 TO PUBLIC;

CREATE PUBLIC SYNONYM T1 FOR T1;
CREATE PUBLIC SYNONYM T2 FOR T2;

Now for the test:

Session 1:

INSERT INTO T1 VALUES(100010);
INSERT INTO T2 VALUES(100010,100010);

Session 2:

INSERT INTO T1 VALUES(100020);
INSERT INTO T2 VALUES(100020,100020);
DELETE FROM T2 WHERE C1=50;
UPDATE T1 SET C1=100030 WHERE C1=50;

{session 2 hangs}

Session 3:

INSERT INTO T1 VALUES(100020);

{session 3 hangs}

Session 4:

SELECT /*+ ORDERED */
  S.SID,
  S.USERNAME,
  S.PROGRAM,
  S.STATUS,
  SW.EVENT,
  SW.WAIT_TIME WT,
  SW.STATE,
  SW.SECONDS_IN_WAIT S_I_W,
  S.SQL_ID,
  S.SQL_ADDRESS,
  S.SQL_HASH_VALUE,
  S.SQL_CHILD_NUMBER,
  S.ROW_WAIT_OBJ# OBJ#,
  S.ROW_WAIT_FILE# FILE#,
  S.ROW_WAIT_BLOCK# BLOCK#,
  S.ROW_WAIT_ROW# ROW#,
  SW.P1,
  SW.P2,
  SW.P3
FROM
  V$SESSION_WAIT SW,
  V$SESSION S
WHERE
  S.USERNAME IS NOT NULL
  AND SW.SID=S.SID
  AND SW.EVENT NOT LIKE '%SQL*Net%'
  AND SW.EVENT NOT IN ('Streams AQ: waiting for messages in the queue', 'wait for unread message on broadcast channel');

SID USERNAME PROGRAM     STATUS EVENT                WT STATE   S_I_W SQL_ID        SQL_ADDR SQL_HV     CN OBJ# FILE# BLOCK# ROW#         P1    P2 P3
--- -------- ----------- ------ -------------------- -- ------- ----- ------------- -------- ---------- -- ---- ----- ------ ---- ---------- ----- --
204 USER2    sqlplus.exe ACTIVE enq: TM - contention  0 WAITING   213 4rtg0hv0atfkx 2AF83608 3232545373  0   -1     0      0    0 1414332421 16472  0
217 USER3    sqlplus.exe ACTIVE enq: TM - contention  0 WAITING   201 cv338j6z2530g 2AF85A5C 3189935119  0   -1     0      0    0 1414332418 16472  0

 

SELECT
  S.SID,
  S.USERNAME,
  S.PROGRAM,
  S.SQL_ID,
  S.SQL_ADDRESS,
  S.SQL_HASH_VALUE,
  S.SQL_CHILD_NUMBER CN,
  S.ROW_WAIT_OBJ#,
  S.ROW_WAIT_FILE#,
  S.ROW_WAIT_BLOCK#,
  S.ROW_WAIT_ROW#,
  L.LMODE,
  L.REQUEST,
  L.ID1,
  L.ID2,
  L.TYPE,
  L.BLOCK
FROM
  V$LOCK L,
  V$SESSION S
WHERE
  (L.ID1, L.ID2, L.TYPE) IN
    (SELECT
      ID1,
      ID2,
      TYPE
    FROM
      V$LOCK
    WHERE
      REQUEST > 0)
  AND L.SID=S.SID;

SID USERNAME PROGRAM     SQL_ID        SQL_ADDR SQL_HASH_VALUE CN ROW_WAIT_OBJ# ROW_WAIT_FILE# ROW_WAIT_BLOCK# ROW_WAIT_ROW# LMODE REQUEST ID1   ID2 TY BLOCK
--- -------- ----------- ------------- -------- -------------- -- ------------- -------------- --------------- ------------- ----- ------- ----- --- -- -----
213 TESTUSER sqlplus.exe 0vbusv12hnbk6 2AF78CC4     1158295110  0             0              2             799             0     3       0 16472   0 TM     1
204 USER2    sqlplus.exe 4rtg0hv0atfkx 2AF83608     3232545373  0            -1              0               0             0     3       5 16472   0 TM     0
217 USER3    sqlplus.exe cv338j6z2530g 2AF85A5C     3189935119  0            -1              0               0             0     0       2 16472   0 TM     0

Session 1:

COMMIT;

Session 3:

{session 3 remains hung}

Session 2:

1 row updated.

Session 4:

SELECT /*+ ORDERED */
  S.SID,
  S.USERNAME,
  S.PROGRAM,
  S.STATUS,
  SW.EVENT,
  SW.WAIT_TIME WT,
  SW.STATE,
  SW.SECONDS_IN_WAIT S_I_W,
  S.SQL_ID,
  S.SQL_ADDRESS,
  S.SQL_HASH_VALUE,
  S.SQL_CHILD_NUMBER,
  S.ROW_WAIT_OBJ# OBJ#,
  S.ROW_WAIT_FILE# FILE#,
  S.ROW_WAIT_BLOCK# BLOCK#,
  S.ROW_WAIT_ROW# ROW#,
  SW.P1,
  SW.P2,
  SW.P3
FROM
  V$SESSION_WAIT SW,
  V$SESSION S
WHERE
  S.USERNAME IS NOT NULL
  AND SW.SID=S.SID
  AND SW.EVENT NOT LIKE '%SQL*Net%'
  AND SW.EVENT NOT IN ('Streams AQ: waiting for messages in the queue', 'wait for unread message on broadcast channel');

SID USERNAME PROGRAM     STATUS EVENT                         WT STATE   S_I_W SQL_ID        SQL_ADDR SQL_HV     CN OBJ# FILE# BLOCK# ROW#         P1     P2   P3
--- -------- ----------- ------ ----------------------------- -- ------- ----- ------------- -------- ---------- -- ---- ----- ------ ---- ---------- ------ ----
217 USER3    sqlplus.exe ACTIVE enq: TX - row lock contention  0 WAITING   380 cv338j6z2530g 2AF85A5C 3189935119  0   -1     0      0    0 1415053316 458788 1297

 

SELECT
  S.SID,
  S.USERNAME,
  S.PROGRAM,
  S.SQL_ID,
  S.SQL_ADDRESS,
  S.SQL_HASH_VALUE,
  S.SQL_CHILD_NUMBER CN,
  S.ROW_WAIT_OBJ#,
  S.ROW_WAIT_FILE#,
  S.ROW_WAIT_BLOCK#,
  S.ROW_WAIT_ROW#,
  L.LMODE,
  L.REQUEST,
  L.ID1,
  L.ID2,
  L.TYPE,
  L.BLOCK
FROM
  V$LOCK L,
  V$SESSION S
WHERE
  (L.ID1, L.ID2, L.TYPE) IN
    (SELECT
      ID1,
      ID2,
      TYPE
    FROM
      V$LOCK
    WHERE
      REQUEST > 0)
  AND L.SID=S.SID;

SID USERNAME PROGRAM     SQL_ID        SQL_ADDR SQL_HASH_VALUE CN ROW_WAIT_OBJ# ROW_WAIT_FILE# ROW_WAIT_BLOCK# ROW_WAIT_ROW# LMODE REQUEST ID1    ID2  TY BLOCK
--- -------- ----------- ------------- -------- -------------- -- ------------- -------------- --------------- ------------- ----- ------- ------ ---- -- -----
217 USER3    sqlplus.exe cv338j6z2530g 2AF85A5C     3189935119  0            -1              0               0             0     0       4 458788 1297 TX     0
204 USER2    sqlplus.exe               00                    0               -1              0               0             0     6       0 458788 1297 TX     1

So, why did Oracle 11.1.0.7 deadlock while 10.2.0.4 did not deadlock (11.1.0.6 and 11.2.0.1 will also deadlock)?





DATE Datatype Or NUMBER Datatype – Which Should be Used?

6 01 2010

January 6, 2010

In a recent discussion thread on the Oracle forums, the following question was asked:
http://forums.oracle.com/forums/thread.jspa?threadID=1007653

I have a scenario where I need to store the data in the format YYYYMM (e.g. 201001 which means January, 2010).  I am trying to evaluate what is the most appropriate datatype to store this kind of data. I am comparing 2 options, NUMBER and DATE.  As the data is essentially a component of oracle date datatype and experts like Tom Kyte have proved (with examples) that using right datatype is better for optimizer. So I was expecting that using DATE datatype will yield (at least) similar (if not better) cardinality estimates than using NUMBER datatype. However, my tests show that when using DATE the cardinality estimates are way off from actuals whereas sing NUMBER the cardinality estimates are much closer to actuals.
My questions are:
1) What should be the most appropriate datatype used to store YYYYMM data?
2) Why does using DATE datatype yield estimates that are way off from actuals than using NUMBER datatype?

Test case (update Jan 7, 2010 : there was a copy-paste error in the line for collecting statistics on table B – the original version of the script posted here collected statistics on table A twice):

create table a nologging as select to_number(to_char(add_months(to_date('200101','YYYYMM'),level - 1), 'YYYYMM')) id from dual connect by level <= 289;

create table b (id number) ;

begin
  for i in 1..8192
  loop
    insert into b select * from a ;
  end loop;
  commit;
end;
/ 

alter table a add dt date;

alter table b add dt date;

update a set dt = to_date(id, 'YYYYMM');

update b set dt = to_date(id, 'YYYYMM');

commit;

exec dbms_stats.gather_table_stats(user, 'A', estimate_percent=>NULL);

exec dbms_stats.gather_table_stats(user, 'B', estimate_percent=>NULL);

explain plan for select count(*) from b where id between 200810 and 200903;
select * from table(dbms_xplan.display);

explain plan for select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM');
select * from table(dbms_xplan.display);

This is an interesting problem, why would using the NUMBER datatype yield better cardinality estimates than the example with the DATE datatype?  When the NUMBER datatype was used, the optimizer predicted that the full table scan operation would return 46,604 rows, while the optimizer predicted that the full table scan would return 5,919 rows when the DATE datatype was used – the actual number of rows returned is 49,152.

The person who posted the above test case later stated that he believes that the DATE datatype is the correct choice, but he would have a difficult time justifying that opinion when confronted by someone suggesting the use of the NUMBER data type.

I posted the test results from my run with Oracle 11.1.0.7:

SQL> set autotrace traceonly explain
SQL> select count(*) from b where id between 200810 and 200903 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 749587668

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     5 |  4715   (1)| 00:00:57 |
|   1 |  SORT AGGREGATE    |      |     1 |     5 |            |          |
|*  2 |   TABLE ACCESS FULL| B    |   108K|   527K|  4715   (1)| 00:00:57 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("ID"<=200903 AND "ID">=200810)

SQL> select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM') ;

Execution Plan
----------------------------------------------------------
Plan hash value: 749587668

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     8 |  4718   (2)| 00:00:57 |
|   1 |  SORT AGGREGATE    |      |     1 |     8 |            |          |
|*  2 |   TABLE ACCESS FULL| B    | 57166 |   446K|  4718   (2)| 00:00:57 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("DT"<=TO_DATE(' 2009-03-01 00:00:00', 'syyyy-mm-dd
              hh24:mi:ss') AND "DT">=TO_DATE(' 2008-10-01 00:00:00', 'syyyy-mm-dd
              hh24:mi:ss'))

SQL> set autotrace off
SQL> select count(*) from b where id between 200810 and 200903 ;

  COUNT(*)
----------
     49152

SQL> select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM') ;

  COUNT(*)
----------
     49152 

Well, it seems that Oracle 11.1.0.7 predicted that when the NUMBER datatype was used, the full table scan would return roughly 108,000 rows.  Oracle 11.1.0.7 predicted that when the DATE datatype was used, the full table scan would return 57,166 rows – significantly closer to the actual number of 49,152.  If there were an index on that column, how would the different cardinality estimates affect the possibility that the optimizer might select to use that index rather than a full table scan?  What if the data volume were increased by a factor of, say 1,000 or 1,000,000?

I also captured a 10053 trace during the test run, and found this in the trace file:

******************************************
----- Current SQL Statement for this session (sql_id=7uk18xj0z9uxf) -----
select count(*) from b where id between 200810 and 200903
*******************************************
...
***************************************
SINGLE TABLE ACCESS PATH
  Single Table Cardinality Estimation for B[B]

  Table: B  Alias: B
    Card: Original: 2367488.000000  Rounded: 108124  Computed: 108124.16  Non Adjusted: 108124.16
  Access Path: TableScan
    Cost:  4714.53  Resp: 4714.53  Degree: 0
      Cost_io: 4670.00  Cost_cpu: 636216240
      Resp_io: 4670.00  Resp_cpu: 636216240
  Best:: AccessPath: TableScan
         Cost: 4714.53  Degree: 1  Resp: 4714.53  Card: 108124.16  Bytes: 0

***************************************
...
...
...
******************************************
----- Current SQL Statement for this session (sql_id=2ac0k15zjdg5x) -----
select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM')
*******************************************
...

***************************************
SINGLE TABLE ACCESS PATH
  Single Table Cardinality Estimation for B[B]

  Table: B  Alias: B
    Card: Original: 2367488.000000  Rounded: 57166  Computed: 57165.51  Non Adjusted: 57165.51
  Access Path: TableScan
    Cost:  4717.89  Resp: 4717.89  Degree: 0
      Cost_io: 4670.00  Cost_cpu: 684264079
      Resp_io: 4670.00  Resp_cpu: 684264079
  Best:: AccessPath: TableScan
         Cost: 4717.89  Degree: 1  Resp: 4717.89  Card: 57165.51  Bytes: 0

***************************************

Notice in the above that Oracle’s statistics gathering process did not create histograms when I collected statistics for the tables.  The calculated cost is the same for either datatype, but what would happen if that table were then joined to another table?  Is the optimizer seeing histograms in some of the original poster’s test cases?

The original poster is running Oracle Database 10.2.0.1, so I ran a test on Oracle 10.2.0.2 with OPTIMIZER_FEATURES_ENABLE set to 10.2.0.1, using the data created by the OP’s data creation script:

ALTER SESSION SET TRACEFILE_IDENTIFIER = 'DateTest';
ALTER SESSION SET EVENTS '10053 trace name context forever, level 1';

select count(*) from b where id between 200810 and 200903 ;

select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM') ;

ALTER SESSION SET EVENTS '10053 trace name context off';

set autotrace traceonly explain

select count(*) from b where id between 200810 and 200903 ;

select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM') ;

set autotrace off

select count(*) from b where id between 200810 and 200903;

The output from the above script, when run on Oracle 10.2.0.2 follows:

SQL> select count(*) from b where id between 200810 and 200903 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 749587668

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     5 |   919  (11)| 00:00:05 |
|   1 |  SORT AGGREGATE    |      |     1 |     5 |            |          |
|*  2 |   TABLE ACCESS FULL| B    |   108K|   527K|   919  (11)| 00:00:05 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("ID"<=200903 AND "ID">=200810)

SQL> select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM') ;

Execution Plan
----------------------------------------------------------
Plan hash value: 749587668

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     8 |   926  (12)| 00:00:05 |
|   1 |  SORT AGGREGATE    |      |     1 |     8 |            |          |
|*  2 |   TABLE ACCESS FULL| B    | 57166 |   446K|   926  (12)| 00:00:05 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("DT"<=TO_DATE('2009-03-01 00:00:00', 'yyyy-mm-dd
              hh24:mi:ss') AND "DT">=TO_DATE('2008-10-01 00:00:00', 'yyyy-mm-dd
              hh24:mi:ss'))

SQL> set autotrace off

SQL> select count(*) from b where id between 200810 and 200903 ;

  COUNT(*)
----------
     49152 

The estimated cardinalities appear to be identical to that of Oracle 11.1.0.7, so why was the original poster seeing different cardinality estimates?  Here is the output from the 10053 trace file:

******************************************
Current SQL statement for this session:
select count(*) from b where id between 200810 and 200903
*******************************************
...
  PARAMETERS WITH ALTERED VALUES
  ******************************
  optimizer_features_enable           = 10.2.0.1
  *********************************
...
BASE STATISTICAL INFORMATION
***********************
Table Stats::
  Table:  B  Alias:  B
    #Rows: 2367488  #Blks:  16725  AvgRowLen:  13.00
***************************************
SINGLE TABLE ACCESS PATH
  Column (#1): ID(NUMBER)
    AvgLen: 5.00 NDV: 289 Nulls: 0 Density: 0.0034602 Min: 200101 Max: 202501
  Table:  B  Alias: B    
    Card: Original: 2367488  Rounded: 108124  Computed: 108124.16  Non Adjusted: 108124.16
  Access Path: TableScan
    Cost:  918.67  Resp: 918.67  Degree: 0
      Cost_io: 819.00  Cost_cpu: 632570063
      Resp_io: 819.00  Resp_cpu: 632570063
  Best:: AccessPath: TableScan
         Cost: 918.67  Degree: 1  Resp: 918.67  Card: 108124.16  Bytes: 0
***************************************
...
...
...
******************************************
Current SQL statement for this session:
select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM')
*******************************************
...
  *************************************
  PARAMETERS WITH ALTERED VALUES
  ******************************
  optimizer_features_enable           = 10.2.0.1
  *********************************
...
BASE STATISTICAL INFORMATION
***********************
Table Stats::
  Table:  B  Alias:  B
    #Rows: 2367488  #Blks:  16725  AvgRowLen:  13.00
***************************************
SINGLE TABLE ACCESS PATH
  Column (#2): DT(DATE)
    AvgLen: 8.00 NDV: 289 Nulls: 0 Density: 0.0034602 Min: 2451911 Max: 2460677
  Table:  B  Alias: B    
    Card: Original: 2367488  Rounded: 57166  Computed: 57165.51  Non Adjusted: 57165.51
  Access Path: TableScan
    Cost:  926.24  Resp: 926.24  Degree: 0
      Cost_io: 819.00  Cost_cpu: 680617902
      Resp_io: 819.00  Resp_cpu: 680617902
  Best:: AccessPath: TableScan
         Cost: 926.24  Degree: 1  Resp: 926.24  Card: 57165.51  Bytes: 0

Notice that no histograms were collected based on the 10053 trace file.

Now a second test, this time we will instruct Oracle to create histograms, and also force the optimizer to hard parse the SQL statements that reference table B when those SQL statements are re-executed:

exec dbms_stats.gather_table_stats(user, 'B', estimate_percent=>NULL,method_opt=>'FOR ALL COLUMNS SIZE 254',no_invalidate=>false);

ALTER SESSION SET TRACEFILE_IDENTIFIER = 'DateTest2';
ALTER SESSION SET EVENTS '10053 trace name context forever, level 1';

select count(*) from b where id between 200810 and 200903 ;

select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM');

ALTER SESSION SET EVENTS '10053 trace name context off';

set autotrace traceonly explain

select count(*) from b where id between 200810 and 200903;

select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM');

set autotrace off

So, what is the output of the above?

SQL> select count(*) from b where id between 200810 and 200903;

Execution Plan
----------------------------------------------------------
Plan hash value: 749587668

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     5 |   919  (11)| 00:00:05 |
|   1 |  SORT AGGREGATE    |      |     1 |     5 |            |          |
|*  2 |   TABLE ACCESS FULL| B    | 46604 |   227K|   919  (11)| 00:00:05 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("ID"<=200903 AND "ID">=200810)

SQL> select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM');

Execution Plan
----------------------------------------------------------
Plan hash value: 749587668

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     8 |   926  (12)| 00:00:05 |
|   1 |  SORT AGGREGATE    |      |     1 |     8 |            |          |
|*  2 |   TABLE ACCESS FULL| B    | 46604 |   364K|   926  (12)| 00:00:05 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("DT"<=TO_DATE('2009-03-01 00:00:00', 'yyyy-mm-dd
              hh24:mi:ss') AND "DT">=TO_DATE('2008-10-01 00:00:00', 'yyyy-mm-dd
              hh24:mi:ss')) 

Interesting, both queries estimate that the full table scan operation will return 46,604 rows – interesting.  That cardinality estimate exactly matches the cardinality estimate in the OP’s plan for the SQL statement that accessed the NUMBER datatype…

For fun, let’s look in the 10053 trace file:

******************************************
Current SQL statement for this session:
select count(*) from b where id between 200810 and 200903
*******************************************
...
BASE STATISTICAL INFORMATION
***********************
Table Stats::
  Table:  B  Alias:  B
    #Rows: 2367488  #Blks:  16725  AvgRowLen:  13.00
***************************************
SINGLE TABLE ACCESS PATH
  Column (#1): ID(NUMBER)
    AvgLen: 5.00 NDV: 289 Nulls: 0 Density: 0.0034602 Min: 200101 Max: 202501
    Histogram: HtBal  #Bkts: 254  UncompBkts: 254  EndPtVals: 255
  Table:  B  Alias: B    
    Card: Original: 2367488  Rounded: 46604  Computed: 46604.09  Non Adjusted: 46604.09
  Access Path: TableScan
    Cost:  918.76  Resp: 918.76  Degree: 0
      Cost_io: 819.00  Cost_cpu: 633149246
      Resp_io: 819.00  Resp_cpu: 633149246
  Best:: AccessPath: TableScan
         Cost: 918.76  Degree: 1  Resp: 918.76  Card: 46604.09  Bytes: 0
...
...
...
******************************************
Current SQL statement for this session:
select count(*) from b where dt between to_date(200810, 'YYYYMM') and to_date(200903, 'YYYYMM')
*******************************************
...
BASE STATISTICAL INFORMATION
***********************
Table Stats::
  Table:  B  Alias:  B
    #Rows: 2367488  #Blks:  16725  AvgRowLen:  13.00
***************************************
SINGLE TABLE ACCESS PATH
  Column (#2): DT(DATE)
    AvgLen: 8.00 NDV: 289 Nulls: 0 Density: 0.0034602 Min: 2451911 Max: 2460677
    Histogram: HtBal  #Bkts: 254  UncompBkts: 254  EndPtVals: 255
  Table:  B  Alias: B    
    Card: Original: 2367488  Rounded: 46604  Computed: 46604.09  Non Adjusted: 46604.09
  Access Path: TableScan
    Cost:  926.22  Resp: 926.22  Degree: 0
      Cost_io: 819.00  Cost_cpu: 680499006
      Resp_io: 819.00  Resp_cpu: 680499006
  Best:: AccessPath: TableScan
         Cost: 926.22  Degree: 1  Resp: 926.22  Card: 46604.09  Bytes: 0

The 10053 trace file shows that in both cases a height balanced histogram with 254 buckets was created.  But, how accurate would the estimate be if there were 1,000 or 1,000,000 times as many rows?  What if the time interval were changed to something else?  What if each of the 289 distinct values for the ID and DT columns did not have an equal distribution of values?

So, why select a DATE datatype rather than a NUMBER datatype?  These are the reasons that I proposed in the discussion thread:

One of the problems with putting date values in number columns is this – if you select the range from 200810 to 200903, the optimizer will likely make the assumption that 200810 is just as likely of a number as 200808, 200812, 200814, 200816, 200820, 200890, 200900, etc. Some of those year/month combinations are simply not possible. In such a case, the optimizer should over-estimate the number of rows returned from that range when the column data type is NUMBER, and should be reasonably close when the column data type is DATE, since the optimizer knows that 200814 (14/1/2008), 200816 (16/1/2008), 200820 (20/1/2008), 200890 (90/1/2008), 200900 (0/1/2009), etc. could never be dates (and would be completely out of the serial sequence of dates). By putting the date type data into a DATE column, you have essentially added a constraint to the database to prevent invalid dates from being added. Additionally, date math, such as finding the number of days between 200802 and 200803 (compared to 200702 and 200703) is very simple – the answer is not 1 in both cases, but rather 29 and 28, respectively.

Any other comments?

OK, enough guessing, let’s try a couple tests.  Here is the test table, with 10,000,000 rows with an uneven distribution of rows for each value:

DROP TABLE B PURGE;

CREATE TABLE B AS
SELECT
  TO_NUMBER(TO_CHAR(TRUNC(TO_DATE('200101','YYYYDD')+SQRT(ROWNUM),'MM'),'YYYYMM')) ID,
  TRUNC(TO_DATE('200101','YYYYDD')+SQRT(ROWNUM),'MM') DT
FROM
  (SELECT
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=1000) V1,
  (SELECT
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=10000) V2;

CREATE INDEX IND_B_ID ON B(ID);
CREATE INDEX IND_B_DT ON B(DT);

SET LINESIZE 130
SET PAGESIZE 1000

The first test script with no histograms, supplying various ranges for year-month combinations while gathering execution statistics, and displaying the actual execution plans:

EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'B',CASCADE=>TRUE,METHOD_OPT=>'FOR ALL COLUMNS SIZE 1',NO_INVALIDATE=>FALSE);

SPOOL HISTOGRAMTEST.TXT

SELECT COUNT(*) FROM B;

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200810 AND 200903;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200810,'YYYYMM') AND TO_DATE(200903,'YYYYMM');

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200110 AND 200203;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200110,'YYYYMM') AND TO_DATE(200203,'YYYYMM');

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200210 AND 200303;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200210,'YYYYMM') AND TO_DATE(200303,'YYYYMM');

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200812 AND 200901;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200812,'YYYYMM') AND TO_DATE(200901,'YYYYMM');

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200112 AND 200201;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200112,'YYYYMM') AND TO_DATE(200201,'YYYYMM');

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200612 AND 200901;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200612,'YYYYMM') AND TO_DATE(200901,'YYYYMM');

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));

SPOOL OFF

The trimmed output (from Oracle 11.1.0.7) of the first date range follows:

SQL> SELECT COUNT(*) FROM B;

  COUNT(*)                                                                                                                       
----------                                                                                                                       
  10000000                                                                                                                       

SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200810 AND 200903;

----------------------------------------------------------------------------------------                                         
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                         
----------------------------------------------------------------------------------------                                         
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.06 |    2371 |                                         
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.06 |    2371 |                                         
|*  2 |   INDEX RANGE SCAN| IND_B_ID |      1 |   1310K|   1063K|00:00:00.01 |    2371 |                                         
----------------------------------------------------------------------------------------                                         

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - access("ID">=200810 AND "ID"<=200903)                                                                                     

SQL>                                                                                                                                   
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200810,'YYYYMM') AND TO_DATE(200903,'YYYYMM');

----------------------------------------------------------------------------------------                                         
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                         
----------------------------------------------------------------------------------------                                         
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.06 |    2816 |                                         
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.06 |    2816 |                                         
|*  2 |   INDEX RANGE SCAN| IND_B_DT |      1 |    674K|   1063K|00:00:00.01 |    2816 |                                         
----------------------------------------------------------------------------------------                                         

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - access("DT">=TO_DATE(' 2008-10-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')                                                    
              AND "DT"<=TO_DATE(' 2009-03-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))                                               

In the above, the estimated number of rows with the comparison on the numeric column is about 250,000 rows above the actual, while the comparison on the date column is about 400,000 rows below the actual – could this be enough of a difference to change the execution plan if the clustering factor of the indexes were high?  What if the tables had a larger average row length?  What if this table were joined with another table?  Note that the number of logical blocks accessed is less with the index on the numeric column.

The trimmed output of the second date range follows:

SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200110 AND 200203;

----------------------------------------------------------------------------------------                                         
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                         
----------------------------------------------------------------------------------------                                         
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.01 |     299 |                                         
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.01 |     299 |                                         
|*  2 |   INDEX RANGE SCAN| IND_B_ID |      1 |   1344K|    132K|00:00:00.01 |     299 |                                         
----------------------------------------------------------------------------------------                                         

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - access("ID">=200110 AND "ID"<=200203)                                                                                     

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200110,'YYYYMM') AND TO_DATE(200203,'YYYYMM');

----------------------------------------------------------------------------------------                                         
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                         
----------------------------------------------------------------------------------------                                         
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.01 |     353 |                                         
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.01 |     353 |                                         
|*  2 |   INDEX RANGE SCAN| IND_B_DT |      1 |    674K|    132K|00:00:00.01 |     353 |                                         
----------------------------------------------------------------------------------------                                         

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - access("DT">=TO_DATE(' 2001-10-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')                                                    
              AND "DT"<=TO_DATE(' 2002-03-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))                                               

In the above, the estimated number of rows with the comparison on the numeric column is about 10 times as high as the actual number of rows, while the estimated number of rows with the comparison on the date column is about 5 times as high.  Could this be enough to trigger a different execution plan for the queries – where one uses an index access, while the other uses a full table scan?

The trimmed output of the third date range follows:

SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200210 AND 200303;

----------------------------------------------------------------------------------------                                         
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                         
----------------------------------------------------------------------------------------                                         
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.03 |     594 |                                         
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.03 |     594 |                                         
|*  2 |   INDEX RANGE SCAN| IND_B_ID |      1 |   1344K|    265K|00:00:00.01 |     594 |                                         
----------------------------------------------------------------------------------------                                         

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - access("ID">=200210 AND "ID"<=200303)                                                                                     

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200210,'YYYYMM') AND TO_DATE(200303,'YYYYMM');

----------------------------------------------------------------------------------------                                         
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                         
----------------------------------------------------------------------------------------                                         
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.03 |     705 |                                         
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.03 |     705 |                                         
|*  2 |   INDEX RANGE SCAN| IND_B_DT |      1 |    674K|    265K|00:00:00.01 |     705 |                                         
----------------------------------------------------------------------------------------                                         

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - access("DT">=TO_DATE(' 2002-10-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')                                                    
              AND "DT"<=TO_DATE(' 2003-03-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))                                               

In this case the estimates are about the same as the previous test, but the actual number of rows has doubled.  The optimizer’s estimates are again in favor of the date datatype.

The trimmed output of the fourth date range follows:

SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200812 AND 200901;

----------------------------------------------------------------------------------------                                         
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                         
----------------------------------------------------------------------------------------                                         
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.03 |     810 |                                         
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.03 |     810 |                                         
|*  2 |   INDEX RANGE SCAN| IND_B_ID |      1 |   1285K|    362K|00:00:00.01 |     810 |                                         
----------------------------------------------------------------------------------------                                         

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - access("ID">=200812 AND "ID"<=200901)                                                                                     

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200812,'YYYYMM') AND TO_DATE(200901,'YYYYMM');

----------------------------------------------------------------------------------------                                         
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                         
----------------------------------------------------------------------------------------                                         
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.03 |     962 |                                         
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.03 |     962 |                                         
|*  2 |   INDEX RANGE SCAN| IND_B_DT |      1 |    291K|    362K|00:00:00.01 |     962 |                                         
----------------------------------------------------------------------------------------                                         

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - access("DT">=TO_DATE(' 2008-12-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')                                                    
              AND "DT"<=TO_DATE(' 2009-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))                                               

This time, the optimizer’s estimate when the date datatype was used is very close, while the optimizer’s estimate when the numeric datatype was used is about 4 times greater than the actual number of rows.

The trimmed output of the fifth date range follows:

SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200112 AND 200201;

----------------------------------------------------------------------------------------                                         
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                         
----------------------------------------------------------------------------------------                                         
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.03 |     104 |                                         
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.03 |     104 |                                         
|*  2 |   INDEX RANGE SCAN| IND_B_ID |      1 |   1295K|  45260 |00:00:00.01 |     104 |                                         
----------------------------------------------------------------------------------------                                         

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - access("ID">=200112 AND "ID"<=200201)                                                                                     

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200112,'YYYYMM') AND TO_DATE(200201,'YYYYMM');

----------------------------------------------------------------------------------------                                         
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                         
----------------------------------------------------------------------------------------                                         
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.01 |     122 |                                         
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.01 |     122 |                                         
|*  2 |   INDEX RANGE SCAN| IND_B_DT |      1 |    291K|  45260 |00:00:00.01 |     122 |                                         
----------------------------------------------------------------------------------------                                         

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - access("DT">=TO_DATE(' 2001-12-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')                                                    
              AND "DT"<=TO_DATE(' 2002-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))                                               

This time with the numeric datatype, the optimizer is estimating 1,295,000 rows when in fact only 45,260 are returned during the index range scan.  The estimate with the date datatype is also quite high, but it is 4 to 5 times lower (thus closer to the actual) than with the numeric datatype.

Now, the final trimmed output:

SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200612 AND 200901;

--------------------------------------------------------------------------------------------                                     
| Id  | Operation             | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                     
--------------------------------------------------------------------------------------------                                     
|   0 | SELECT STATEMENT      |          |      1 |        |      1 |00:00:00.78 |   22345 |                                     
|   1 |  SORT AGGREGATE       |          |      1 |      1 |      1 |00:00:00.78 |   22345 |                                     
|*  2 |   INDEX FAST FULL SCAN| IND_B_ID |      1 |   3764K|   4054K|00:00:00.22 |   22345 |                                     
--------------------------------------------------------------------------------------------                                     

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - filter(("ID">=200612 AND "ID"<=200901))                                                                                   

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200612,'YYYYMM') AND TO_DATE(200901,'YYYYMM');

-------------------------------------------------------------------------------------                                            
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |                                            
-------------------------------------------------------------------------------------                                            
|   0 | SELECT STATEMENT   |      |      1 |        |      1 |00:00:00.47 |   24950 |                                            
|   1 |  SORT AGGREGATE    |      |      1 |      1 |      1 |00:00:00.47 |   24950 |                                            
|*  2 |   TABLE ACCESS FULL| B    |      1 |   2623K|   4054K|00:00:00.12 |   24950 |                                            
-------------------------------------------------------------------------------------                                            

Predicate Information (identified by operation id):                                                                              
---------------------------------------------------                                                                              
   2 - filter(("DT">=TO_DATE(' 2006-12-01 00:00:00', 'syyyy-mm-dd                                                                
              hh24:mi:ss') AND "DT"<=TO_DATE(' 2009-01-01 00:00:00', 'syyyy-mm-dd                                                
              hh24:mi:ss'))) 

Here, with the wider date range, the optimizer is closer with the numeric data type, and has selected to perform a fast full scan of the index, which would use multi-block reads if disk accesses were required.  Note that the optimizer selected to perform a full table scan when the date datatype was used, even though the estimated number of rows was less.  Note too that this in-memory operation completed about twice as fast as the in-memory index fast full scan operation.

Now let’s take a look at what happens when a histogram is present on each of the columns.  The script is identical to the previous script, except for the first two lines:

EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'B',CASCADE=>TRUE,METHOD_OPT=>'FOR ALL COLUMNS SIZE 254',NO_INVALIDATE=>FALSE);

SPOOL HISTOGRAMTEST2.TXT

Below are the results from this test run:

SQL> SELECT COUNT(*) FROM B;

  COUNT(*)     
----------     
  10000000     

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200810 AND 200903;

----------------------------------------------------------------------------------------   
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |   
----------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.09 |    2371 |   
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.09 |    2371 |   
|*  2 |   INDEX RANGE SCAN| IND_B_ID |      1 |   1083K|   1063K|00:00:00.01 |    2371 |   
----------------------------------------------------------------------------------------   

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - access("ID">=200810 AND "ID"<=200903)         

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200810,'YYYYMM') AND TO_DATE(200903,'YYYYMM');

----------------------------------------------------------------------------------------   
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |   
----------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.09 |    2816 |   
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.09 |    2816 |   
|*  2 |   INDEX RANGE SCAN| IND_B_DT |      1 |   1083K|   1063K|00:00:00.01 |    2816 |   
----------------------------------------------------------------------------------------   

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - access("DT">=TO_DATE(' 2008-10-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')              
              AND "DT"<=TO_DATE(' 2009-03-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))         

SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200110 AND 200203;

----------------------------------------------------------------------------------------   
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |   
----------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.01 |     299 |   
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.01 |     299 |   
|*  2 |   INDEX RANGE SCAN| IND_B_ID |      1 |    130K|    132K|00:00:00.01 |     299 |   
----------------------------------------------------------------------------------------   

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - access("ID">=200110 AND "ID"<=200203)         

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200110,'YYYYMM') AND TO_DATE(200203,'YYYYMM');

----------------------------------------------------------------------------------------   
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |   
----------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.01 |     353 |   
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.01 |     353 |   
|*  2 |   INDEX RANGE SCAN| IND_B_DT |      1 |    130K|    132K|00:00:00.01 |     353 |   
----------------------------------------------------------------------------------------   

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - access("DT">=TO_DATE(' 2001-10-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')              
              AND "DT"<=TO_DATE(' 2002-03-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))         

SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200210 AND 200303;

----------------------------------------------------------------------------------------   
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |   
----------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.01 |     594 |   
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.01 |     594 |   
|*  2 |   INDEX RANGE SCAN| IND_B_ID |      1 |    284K|    265K|00:00:00.01 |     594 |   
----------------------------------------------------------------------------------------   

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - access("ID">=200210 AND "ID"<=200303)         

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200210,'YYYYMM') AND TO_DATE(200303,'YYYYMM');

----------------------------------------------------------------------------------------   
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |   
----------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.01 |     705 |   
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.01 |     705 |   
|*  2 |   INDEX RANGE SCAN| IND_B_DT |      1 |    284K|    265K|00:00:00.01 |     705 |   
----------------------------------------------------------------------------------------   

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - access("DT">=TO_DATE(' 2002-10-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')              
              AND "DT"<=TO_DATE(' 2003-03-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))         

SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200812 AND 200901;

----------------------------------------------------------------------------------------   
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |   
----------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.01 |     810 |   
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.01 |     810 |   
|*  2 |   INDEX RANGE SCAN| IND_B_ID |      1 |    383K|    362K|00:00:00.01 |     810 |   
----------------------------------------------------------------------------------------   

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - access("ID">=200812 AND "ID"<=200901)         

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200812,'YYYYMM') AND TO_DATE(200901,'YYYYMM');

----------------------------------------------------------------------------------------   
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |   
----------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.03 |     962 |   
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.03 |     962 |   
|*  2 |   INDEX RANGE SCAN| IND_B_DT |      1 |    383K|    362K|00:00:00.01 |     962 |   
----------------------------------------------------------------------------------------   

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - access("DT">=TO_DATE(' 2008-12-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')              
              AND "DT"<=TO_DATE(' 2009-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))         

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200112 AND 200201;

----------------------------------------------------------------------------------------   
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |   
----------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.01 |     104 |   
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.01 |     104 |   
|*  2 |   INDEX RANGE SCAN| IND_B_ID |      1 |  39391 |  45260 |00:00:00.01 |     104 |   
----------------------------------------------------------------------------------------   

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - access("ID">=200112 AND "ID"<=200201)         

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200112,'YYYYMM') AND TO_DATE(200201,'YYYYMM');

----------------------------------------------------------------------------------------   
| Id  | Operation         | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |   
----------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT  |          |      1 |        |      1 |00:00:00.01 |     122 |   
|   1 |  SORT AGGREGATE   |          |      1 |      1 |      1 |00:00:00.01 |     122 |   
|*  2 |   INDEX RANGE SCAN| IND_B_DT |      1 |  39391 |  45260 |00:00:00.01 |     122 |   
----------------------------------------------------------------------------------------   

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - access("DT">=TO_DATE(' 2001-12-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')              
              AND "DT"<=TO_DATE(' 2002-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))         

SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE ID BETWEEN 200612 AND 200901;

--------------------------------------------------------------------------------------------
| Id  | Operation             | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |          |      1 |        |      1 |00:00:00.75 |   22345 |
|   1 |  SORT AGGREGATE       |          |      1 |      1 |      1 |00:00:00.75 |   22345 |
|*  2 |   INDEX FAST FULL SCAN| IND_B_ID |      1 |   4053K|   4054K|00:00:00.22 |   22345 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - filter(("ID">=200612 AND "ID"<=200901))       

SQL>
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*) FROM B WHERE DT BETWEEN TO_DATE(200612,'YYYYMM') AND TO_DATE(200901,'YYYYMM');

-------------------------------------------------------------------------------------      
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |      
-------------------------------------------------------------------------------------      
|   0 | SELECT STATEMENT   |      |      1 |        |      1 |00:00:00.47 |   24950 |      
|   1 |  SORT AGGREGATE    |      |      1 |      1 |      1 |00:00:00.47 |   24950 |      
|*  2 |   TABLE ACCESS FULL| B    |      1 |   4053K|   4054K|00:00:00.12 |   24950 |      
-------------------------------------------------------------------------------------      

Predicate Information (identified by operation id):  
---------------------------------------------------  
   2 - filter(("DT">=TO_DATE(' 2006-12-01 00:00:00', 'syyyy-mm-dd                          
              hh24:mi:ss') AND "DT"<=TO_DATE(' 2009-01-01 00:00:00', 'syyyy-mm-dd          
              hh24:mi:ss')))                         

As the above indicates, with a 254 bucket histogram on both of the columns the optimizer calculates estimated row counts that are typically very close to the actual row counts for both datatypes – essentially the only difference is the number of logical reads.  So, adding the histogram helps, but then what if the OP implements good coding standards and uses bind variables rather than constants (literals)?

So, which datatype would you choose, and why?





Network Monitoring Experimentations 3

5 01 2010

January 5, 2010

(Back to the Previous Post in the Series) (Forward to the Next Post in the Series)

Previously, I showed a couple of examples of network communication working well, as well as a couple counter-examples where the network communication caused problems for Oracle Database communication.  Problems like those shown in the previous articles are not exclusive to just database communication, but instead potentially impact all network communication between two devices attached to the network.

Here is a question that I asked potential job candidates who were interviewing for a position at the company – not so much looking for a correct answer, but rather the thought process demonstrated by the person who was looking to be hired:

—————————————————————————————————–

Situation: All servers are connected to an HP 4160GL 60 port gigabit managed switch using CAT 5e cables.  A new Dell PowerEdge 2850 server running Windows 2003 Standard Edition and with all security updates is installed.  The PowerEdge 2850 indicated that its network card was connected at gigabit speeds, and the HP 4160GL switch also reported that the server was connected at gigabit speeds.  Copying files from the Dell PowerEdge 1750 server running Red Hat Enterprise Linux ES 3 Server running SAMBA was extremely slow.  The PowerEdge 1750 indicated that its network card was connected at gigabit speeds, and the HP 4160GL switch also reports that the server is connected at gigabit speeds.

Initial Analysis: Pull copying the Windows 2000 Service Pack 4 file, which is roughly 132MB in size, to the PowerEdge 2850 (Windows 2003) from the PowerEdge 1750 (RH Linux) using Windows Explorer required more than 45 minutes to complete.  Pull copying the same file to a five year old laptop with a 100Mbps connection running Windows 95 from the PowerEdge 1750 using Windows Explorer required roughly 22 seconds to complete (and 17 seconds on a desktop computer with a 100Mbps connection).  Pull copying the same file to a year old desktop computer with a Gigabit connection running Windows XP from the PowerEdge 1750 using Windows Explorer required roughly 4 seconds to complete.

What would you do to troubleshoot this problem?  What is the cause of the problem?  What is the solution?

This is the start of the problematic transfer, about 1.3 seconds after logging was initiated with Wireshark (actually Ethereal at the time) – IP address .47 is the Windows 2003 Server with Service Pack 1, IP address .46 is the Red Hat Enterprise Linux ES 3 Server w/ SAMBA:

Roughly 4 seconds into the copy, only about 200 packets were logged in the packet capture program:

The problem was corrected and then the transfer was captured again.  This is the start of the fixed run (about 1 second after logging was initiated) – IP address .47 is the Windows 2003 Server with Service Pack 1, IP address .46 is the Red Hat Enterprise Linux ES 3 Server w/ SAMBA:

Roughly 3.4 seconds after the file copy started, the file copy completed.  Roughly 80,745 packets were transmitted across the network during the copy operation:

—————————————————————————————————–

What would you do to troubleshoot this problem?  What is the cause of the problem?  What is the solution?





Simplified Logical and Physical Data Organization

4 01 2010

January 4, 2010

This post shows a rather busy slide from a presentation I gave in 2008.  The slide shows an overview of logical and physical data organization in a basic Oracle database.

One of the keys to being able to administer Oracle databases efficiently is understanding how data in its most basic form is organized in a database.  Table data and its indexes are stored in logical entities called tablespaces.  This diagram depicts two tablespaces, which might be the SYSTEM tablespace and a second tablespace for user data.

Tablespaces store the tables and indexes in one or more datafiles.  There are operating system and Oracle limitations to the size and number of tablespaces.  For instance, with the default settings (8KB block size), there must be more than one data file to contain 33GB of data.

Tables and indexes are two types of segments, both of which may have parts (extents) stored in multiple datafiles for a single tablespace.  As segments grow in size, the parts of segments (extents) are rarely stored in adjacent areas of the data files.  This slide shows 5 tables and 6 indexes for one of those tables in a single tablespace.

Each segment may be composed of one or more extents.  Even though extents for a single segment may be scattered throughout the datafiles, the tablespace is not considered to be suffering from fragmentation unless there are small areas, for instance between E3 and E4, which are too small to contain a new extent.  It is important to standardize on a common extent size for all objects in a tablespace to avoid fragmentation.

Each extent contains 5 or more adjacent data blocks.  This graphic illustrates a 64KB extent size, composed of eight 8KB blocks.

In simple cases, if  a block contains table data, the block will be logically divided into one or more rows, typically with a little empty space to allow the rows to grow later.

Each row is composed of one or more columns, which contain the data stored in the database.  The book “Troubleshooting Oracle Performance” provides evidence with a test case that indicates accessing the left-most columns in a row is more efficient than accessing the right-most columns in the same row.





Eliminate Rows Having a Letter and Number Combination

3 01 2010

January 3, 2010

(Back to the Previous Post in the Series)

In a recent message thread on the comp.databases.oracle.misc group, the following question was asked:

I am currently using Oracle9i Enterprise Edition Release 9.2.0.4.0. I have a table with following data

Table 1 (Sample data)

a12345
A123423
g13452
G452323
h34423
r34323
b23232
n232323

I am currently using this as a subquery in one of the query. As per a new request I have to now exclude all values which start with h, b or n followed by numeric values. So end result the subquery should give me is

Table 1 (Sample data)

a12345
A123423
g13452
G452323
r34323

I am little stumped on this for now. Could not get it right in my query. Can anyone please advise here. Let me know if any more information is needed from my side.

Note: The starting character in all values can sometimes in “lower case” or sometimes in “upper case”.

Interesting problem, although it would have been helpful had the OP provided the DDL and DML to create the test case.  Let’s see if there is a hard way to solve this problem:

CREATE TABLE T10(HOMEWORK VARCHAR2(20));

INSERT INTO T10 VALUES ('a12345');
INSERT INTO T10 VALUES ('A123423');
INSERT INTO T10 VALUES ('g13452');
INSERT INTO T10 VALUES ('G452323');
INSERT INTO T10 VALUES ('h34423');
INSERT INTO T10 VALUES ('r34323');
INSERT INTO T10 VALUES ('b23232');
INSERT INTO T10 VALUES ('n232323');
INSERT INTO T10 VALUES ('NB151517');
INSERT INTO T10 VALUES ('C0151517');
INSERT INTO T10 VALUES ('f9151517');
INSERT INTO T10 VALUES ('HE4423');

COMMIT;

Note that I added a couple of extra rows just for fun (actually to help with testing).

Let’s look at the ASCII values of the first and second characters:

SELECT
  HOMEWORK,
  ASCII(SUBSTR(HOMEWORK,1,1)) ASC_VAL1,
  ASCII(SUBSTR(HOMEWORK,2,1)) ASC_VAL2
FROM
  T10;

HOMEWORK     ASC_VAL1   ASC_VAL2
---------- ---------- ----------
a12345             97         49
A123423            65         49
g13452            103         49
G452323            71         52
h34423            104         51
r34323            114         51
b23232             98         50
n232323           110         50
NB151517           78         66
C0151517           67         48
f9151517          102         57
HE4423             72         69

OK, I see the ones that we want to exclude, let’s build a matrix:

SELECT
  HOMEWORK,
  ASCII(SUBSTR(HOMEWORK,1,1)) ASC_VAL1,
  ASCII(SUBSTR(HOMEWORK,2,1)) ASC_VAL2,
  DECODE(ASCII(SUBSTR(HOMEWORK,1,1)),104,1,72,1,66,1,98,1,78,1,110,1,0) IS_EXC1,
  DECODE(SIGN(ASCII(SUBSTR(HOMEWORK,2,1))-47),1,DECODE(SIGN(ASCII(SUBSTR(HOMEWORK,2,1))-58),-1,1,0),0) IS_EXC2
FROM
  T10;

HOMEWORK     ASC_VAL1   ASC_VAL2    IS_EXC1    IS_EXC2
---------- ---------- ---------- ---------- ----------
a12345             97         49          0          1
A123423            65         49          0          1
g13452            103         49          0          1
G452323            71         52          0          1
h34423            104         51          1          1
r34323            114         51          0          1
b23232             98         50          1          1
n232323           110         50          1          1
NB151517           78         66          1          0
C0151517           67         48          0          1
f9151517          102         57          0          1
HE4423             72         69          1          0

If there is a 1 in both of the right-most columns, then the row should be eliminated.  What is the easiest way to tell if there is a 1 in both columns?  Multiply the column values together, and if we receive a product of 1 then the row should be excluded:

SELECT
  *
FROM
  (SELECT
    HOMEWORK,
    ASCII(SUBSTR(HOMEWORK,1,1)) ASC_VAL1,
    ASCII(SUBSTR(HOMEWORK,2,1)) ASC_VAL2,
    DECODE(ASCII(SUBSTR(HOMEWORK,1,1)), 104,1,72,1,66,1,98,1,78,1,110,1,0) IS_EXC1,
    DECODE(SIGN(ASCII(SUBSTR(HOMEWORK,2,1))-47),1,DECODE(SIGN(ASCII(SUBSTR(HOMEWORK,2,1))-58),-1,1,0),0) IS_EXC2
  FROM
    T10)
WHERE
  IS_EXC1*IS_EXC2<>1;

HOMEWORK     ASC_VAL1   ASC_VAL2    IS_EXC1    IS_EXC2
---------- ---------- ---------- ---------- ----------
a12345             97         49          0          1
A123423            65         49          0          1
g13452            103         49          0          1
G452323            71         52          0          1
r34323            114         51          0          1
NB151517           78         66          1          0
C0151517           67         48          0          1
f9151517          102         57          0          1
HE4423             72         69          1          0

An explanation of the IS_EXC2 column follows:

The numbers 0 through 9 have ASCII values ranging from 48 to 57.

  1. Obtain the second character in the column: SUBSTR(HOMEWORK,2,1) 
  2. Use the ASCII function to find the ASCII value of the second character 
  3. Subtract 47 from the ASCII value for the second character
  4. If the difference is greater than 0, then:
    ** Subtract 58 from that ASCII value
  5. If the difference is less than 0, then we found an ASCII value between 48 and 57 – therefore the second character must be a number
    ** Return the number 1 if the ASCII value is between 48 and 57, otherwise return 0

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Maxim Demenko offered the following as a solution to the problem which uses the RTRIM function: 

Just to mention another approach regarding your question:

SQL> with t as (
   2   select 'a12345' c from dual  union all
   3   select 'A123423' from dual  union all
   4   select 'g13452' from dual  union all
   5   select 'G452323' from dual  union all
   6   select 'h34423' from dual  union all
   7   select 'r34323' from dual  union all
   8   select 'b23232' from dual  union all
   9   select 'n' from dual union all
  10   select 'n232323' from dual
  11  )
  12  -- End test data
  13  select c
  14  from t
  15  where not lower(rtrim(c,'0123456789')) in ('h','b','n')
  16  /

C
-------
a12345
A123423
g13452
G452323
r34323

Maxim’s solution is quite impressive.  Here is an explanation of his solution:

SELECT
  *
FROM
  T10;

HOMEWORK
--------
a12345
A123423
g13452
G452323
h34423
r34323
b23232
n232323
NB151517
C0151517
f9151517
HE4423

The demo table has 12 rows.

The first part of his solution does this:

SELECT
  HOMEWORK,
  RTRIM(HOMEWORK,'0123456789') TEST
FROM
  T10;

HOMEWORK   TEST
---------- ----
a12345     a
A123423    A
g13452     g
G452323    G
h34423     h
r34323     r
b23232     b
n232323    n
NB151517   NB
C0151517   C
f9151517   f
HE4423     HE

Notice in the above that the TEST column shows that the RTRIM function eliminated everything to the right of the first digit, including that first digit.  Then, his solution simply determines if what is left (in the TEST column) is one of h, b, or n, and if it is, the row is eliminated.

The output of Maxim’s solution:

SELECT
  HOMEWORK
FROM
  T10
WHERE
  NOT LOWER(RTRIM(HOMEWORK,'0123456789')) IN ('h','b','n');

HOMEWORK
---------
a12345
A123423
g13452
G452323
r34323
NB151517
C0151517
f9151517
HE4423

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Now that we have seen the hard way to solve the problem and a very clever way to solve it, are there any other ways?

A CASE structure could be used rather than the cumbersome nested DECODE and SIGN statements.  A CASE structure will be easier to maintain:

SELECT
  CASE WHEN ASCII(SUBSTR(HOMEWORK,2,1)) >= 48
        AND ASCII(SUBSTR(HOMEWORK,2,1)) <= 57 THEN 1
    ELSE 0 END IS_EXC2
FROM
  T10;

You could transform this section to a CASE structure also:

DECODE(ASCII(SUBSTR(HOMEWORK,1,1)),104,1,72,1,66,1,98,1,78,1,110,1,0) IS_EXC1

 

SELECT
  CASE ASCII(SUBSTR(HOMEWORK,1,1))
    WHEN 104 THEN 1
    WHEN 72 THEN 1
    WHEN 66 THEN 1
    WHEN 98 THEN 1
    WHEN 78 THEN 1
    WHEN 110 THEN 1
    ELSE 0 END IS_EXC1
FROM
  T10;

Finally, you could combine the two CASE structures in the WHERE clause:

SELECT
  HOMEWORK,
  ASCII(SUBSTR(HOMEWORK,1,1)) ASC_VAL1,
  ASCII(SUBSTR(HOMEWORK,2,1)) ASC_VAL2
FROM
  T10
WHERE
  (CASE ASCII(SUBSTR(HOMEWORK,1,1))
    WHEN 104 THEN 1
    WHEN 72 THEN 1
    WHEN 66 THEN 1
    WHEN 98 THEN 1
    WHEN 78 THEN 1
    WHEN 110 THEN 1
    ELSE 0 END) *
  (CASE WHEN ASCII(SUBSTR(HOMEWORK,2,1)) >= 48
        AND ASCII(SUBSTR(HOMEWORK,2,1)) <= 57 THEN 1
    ELSE 0 END) = 0;

HOMEWORK     ASC_VAL1   ASC_VAL2
---------- ---------- ----------
a12345             97         49
A123423            65         49
g13452            103         49
G452323            71         52
r34323            114         51
NB151517           78         66
C0151517           67         48
f9151517          102         57
HE4423             72         69

Here are a couple more solutions:
The silly way with a MINUS operation:

SELECT
  HOMEWORK
FROM
  T10
MINUS
SELECT
  HOMEWORK
FROM
  T10
WHERE
  UPPER(SUBSTR(HOMEWORK,1,1)) IN ('H','B','N')
  AND SUBSTR(HOMEWORK,2,1) IN ('1','2','3','4','5','6','7','8','9','0');

HOMEWORK
--------
A123423
C0151517
G452323
HE4423
NB151517
a12345
f9151517
g13452
r34323

The neat solution with MINUS:

SELECT
  HOMEWORK
FROM
  T10
MINUS
SELECT
  HOMEWORK
FROM
  T10
WHERE
  UPPER(SUBSTR(HOMEWORK,1,1)) IN ('H','B','N')
  AND SUBSTR(HOMEWORK,2,1) IN (
    SELECT
      TO_CHAR(ROWNUM-1)
    FROM
      DUAL
    CONNECT BY
      LEVEL<=10);

HOMEWORK
--------
A123423
C0151517
G452323
HE4423
NB151517
a12345
f9151517
g13452
r34323

The NOT method:

SELECT
  HOMEWORK
FROM
  T10
WHERE
  NOT(UPPER(SUBSTR(HOMEWORK,1,1)) IN ('H','B','N')
    AND SUBSTR(HOMEWORK,2,1) IN
('1','2','3','4','5','6','7','8','9','0'));

HOMEWORK
--------
a12345
A123423
g13452
G452323
r34323
NB151517
C0151517
f9151517
HE4423

The neat solution with NOT:

SELECT
  HOMEWORK
FROM
  T10
WHERE
  NOT(UPPER(SUBSTR(HOMEWORK,1,1)) IN ('H','B','N')
    AND SUBSTR(HOMEWORK,2,1) IN (
      SELECT
        TO_CHAR(ROWNUM-1)
      FROM
        DUAL
      CONNECT BY
        LEVEL<=10));

HOMEWORK
--------
a12345
A123423
g13452
G452323
r34323
NB151517
C0151517
f9151517
HE4423

The left outer join method:

SELECT
  T10.HOMEWORK
FROM
  T10,
  (SELECT
    HOMEWORK
  FROM
    T10
  WHERE
    (UPPER(SUBSTR(HOMEWORK,1,1)) IN ('H','B','N'))
    AND (SUBSTR(HOMEWORK,2,1) IN (
      SELECT
        TO_CHAR(ROWNUM-1)
      FROM
        DUAL
      CONNECT BY
        LEVEL<=10))) NT10
WHERE
  T10.HOMEWORK=NT10.HOMEWORK(+)
  AND NT10.HOMEWORK IS NULL;

HOMEWORK
--------
A123423
C0151517
r34323
HE4423
g13452
f9151517
a12345
G452323
NB151517

The Cartesian join method:

SELECT
  HOMEWORK
FROM
  T10
WHERE
  UPPER(SUBSTR(HOMEWORK,1,2)) NOT IN
(SELECT
  L||N
FROM
  (SELECT
    DECODE(ROWNUM,1,'H',2,'B',3,'N') L
  FROM
    DUAL
  CONNECT BY
    LEVEL<=3),
  (SELECT
    TO_CHAR(ROWNUM-1) N
  FROM
    DUAL
  CONNECT BY
    LEVEL<=10));

HOMEWORK
--------
a12345
A123423
g13452
G452323
r34323
NB151517
C0151517
f9151517
HE4423

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Mark Powell offered the following method using the TRANSLATE function:

Here is a solution that uses a translate function.  My result vary because I could not remember the actual starting letters specified by the OP as I do not have access to Oracle and the forum at the same time.  I made my solution case sensitive and used “b,g, and h”.  I added two rows to ensure at least one row that started with one of the exclude letters when followed by digits whould appear in the output.

1 > select * from t10
  2  where homework not in (
  3    select homework
  4    from t10
  5    where ( substr(homework,1,1) in ('b','g','h')
  6    and instr(translate(homework,'012345678','999999999'),'9') > 0 ))
  7  /

HOMEWORK
--------------------
a12345
A123423
G452323
r34323
n232323
NB151517
C0151517
f9151517
HE4423
hxxxxxxx          -- added
gabcdefg          -- added

11 rows selected.

The above assumes that all the data is of the form Letter || digits and that no data with mixed letters and digits where the presence of letters should cause the data to not be excluded.  The following would handle data with those rules using something like h123x as a test case.

  5    where ( substr(homework,1,1) in (‘b’,’g’,’h’)
  6    and       replace(translate(substr(homework,2,length (homework)),
  7            ‘012345678’,’999999999′),’9′,”) is null

Using an upper or lower rtrim depending on case sensitivity desired as Maxum demostrated does seem a lot slicker of a solution.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If the OP were running Oracle 10g R1 or later the following would also work:
REGEXP_INSTR method:

SELECT
  HOMEWORK
FROM
  T10
WHERE
  REGEXP_INSTR(UPPER(HOMEWORK),'[HBN][0123456789]')<>1;

HOMEWORK
--------
a12345
A123423
g13452
G452323
r34323
NB151517
C0151517
f9151517
HE4423

Shortened version of the above:

SELECT
  HOMEWORK
FROM
  T10
WHERE
  REGEXP_INSTR(UPPER(HOMEWORK),'[HBN][0-9]')<>1

HOMEWORK
--------
a12345
A123423
g13452
G452323
r34323
NB151517
C0151517
f9151517
HE4423

REGEXP_REPLACE method:

SELECT
  HOMEWORK
FROM
  T10
WHERE
  REGEXP_REPLACE(SUBSTR(UPPER(HOMEWORK),1,2),'[HBN][0123456789]',NULL) IS NOT NULL;

HOMEWORK
--------
a12345
A123423
g13452
G452323
r34323
NB151517
C0151517
f9151517
HE4423




Concatenate Text Stored in Byte Arrays within BLOB Type Columns

2 01 2010

January 2, 2010

A couple of days ago I received a question on a private message board regarding how to duplicate the variable length text stored by an ERP system in a BLOB type column.  The ERP system stores the text as a byte array (one byte per character) that is terminated by an ASCII character 0 (a string termination character).  Previous versions of this ERP system actually stored the variable length text in a LONG RAW type column.  BLOB type columns are easier to deal with than LONG RAW columns, so I gave the person a simple INSERT… SELECT to essentially copy the BLOB value from one row to a new row:

INSERT INTO
  OPERATION_BINARY
SELECT
  WORKORDER_TYPE,
  'NEW_BASE_ID',
  'NEW_LOT_ID',
  'NEW_SPLIT_ID',
  'NEW_SUB_ID',
  NEW_SEQUENCE_NO,
  TYPE,
  BITS,
  BITS_LENGTH
FROM
  OPERATION_BINARY
WHERE
  WORKORDER_TYPE='M'
  AND WORKORDER_BASE_ID='ABC123'
  AND WORKORDER_LOT_ID='123'
  AND WORKORDER_SPLIT_ID='0'
  AND WORKORDER_SUB_ID='0'
  AND SEQUENCE_NO=10;

The above is nice and simple, except that it does not accomplish what the person wanted to have happen.

The OP then clarified that he would like to concatenate the text stored in one BLOB type column with the text stored in a BLOB type column found in a different row in the same table:
Row 1 has BLOB bits that says “I AM A NOTE“.
Row 2 has BLOB bits that says “I AM AN ADDITIONAL NOTE.
 
The end result should be Row 1 having a BLOB column with “I AM A NOTE. I AM AN ADDITIONAL NOTE.“, terminated with an ASCII 0 and with the total length of the byte array (including the ASCII 0) recorded in the BITS_LENGTH column.  Easy, right?

To process will be something like this:

  1. Convert the current BLOB value to a VARCHAR2 with UTL_RAW.CAST_TO_VARCHAR2
  2. Append a space to the above result
  3. Convert the BLOB value to be appended to a VARCHAR2 with UTL_RAW.CAST_TO_VARCHAR2 and append to the above result
  4. Append a CHR(0) character to the end of the above result.
  5. Find the length of the above and enter it into the BITS_LENGTH column.
  6. Use UTL_RAW.CAST_TO_RAW to convert the above VARCHAR2 result back into a BLOB and enter it into the BITS column.

Here is how I showed the OP to find a solution (note that I switched to a different table to simplify the explanation):
First, I find the first two rows in the PART_BINARY table with a non-null long description:

SELECT
  PART_ID
FROM
  PART_BINARY
WHERE
  BITS IS NOT NULL
  AND ROWNUM<3;

PART_ID
-------
5P8245
8X8202

Next, I check the current long descriptions:

SELECT
  UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(BITS,32000,1)) BITS
FROM
  PART_BINARY
WHERE
  PART_ID='8X8202';

BITS
-----------------------
FROM VENDOR DEL TO ASSY

 

SELECT
  UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(BITS,32000,1)) BITS
FROM
  PART_BINARY
WHERE
PART_ID='5P8245';

BITS
-----------
(CASE ASSY)

Now an experiment to see what the result would look like:

SELECT
  UTL_RAW.CAST_TO_RAW(UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(PB1.BITS,32000,1)) ||
    ' '||UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(PB2.BITS,32000,1))||CHR(0)),
  LENGTH(UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(PB1.BITS,32000,1)) ||
    ' '||UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(PB2.BITS,32000,1))||CHR(0))
FROM
  PART_BINARY PB1,
  PART_BINARY PB2
WHERE
  PB1.PART_ID='8X8202'
  AND PB2.PART_ID='5P8245';

----------------------------------------------------------
46524F4D2056454E444F522044454C20544F204153535920284341534520415353592900 36

(If you remove the UTL_RAW.CAST_TO_RAW in the above, you will see the actual string).

Now, we can convert the SELECT statement into an UPDATE statement like this:

UPDATE
  PART_BINARY
SET
  (BITS, BITS_LENGTH)=
  (SELECT
    UTL_RAW.CAST_TO_RAW(UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(PB1.BITS,32000,1)) ||
      ' '||UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(PB2.BITS,32000,1))||CHR(0)),
    LENGTH(UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(PB1.BITS,32000,1)) ||
      ' '||UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(PB2.BITS,32000,1))||CHR(0))
    FROM
      PART_BINARY PB1,
      PART_BINARY PB2
    WHERE
      PB1.PART_ID='8X8202'
      AND PB2.PART_ID='5P8245')
WHERE
  PART_ID='8X8202';

Then we can test the result of the UPDATE:

SELECT
  UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(BITS,32000,1)) BITS
FROM
  PART_BINARY
WHERE
  PART_ID='8X8202';

BITS
-----------------------------------
FROM VENDOR DEL TO ASSY (CASE ASSY)

The OP further clarified that the above approach works, but there are possibly 3,000 pairs of rows that need to be combined.  The original approach probably needs to be scrapped.

I then put together the following demonstration:
For experimentation, note that it is possible to do something like the following:

SELECT
  WORKORDER_TYPE,
  WORKORDER_BASE_ID,
  WORKORDER_LOT_ID,
  WORKORDER_SPLIT_ID,
  WORKORDER_SUB_ID,
  SEQUENCE_NO,
  UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(OB.BITS,32000,1)) VARCHAR_BITS,
  LENGTH(UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(OB.BITS,32000,1))) VARCHAR_BITS_LENGTH,
  BITS_LENGTH
FROM
  OPERATION_BINARY OB
WHERE
  TYPE='D'
  AND ROWNUM<=10;

The above code sample converts the first 10 long descriptions from the operation binary table, ready to be used as VARCHAR2 data types.  If you remove the AND ROWNUM<10 restriction, you could easily create a view using the above:

CREATE OR REPLACE VIEW OPERATION_BINARY_VIEW AS
SELECT
  WORKORDER_TYPE,
  WORKORDER_BASE_ID,
  WORKORDER_LOT_ID,
  WORKORDER_SPLIT_ID,
  WORKORDER_SUB_ID,
  SEQUENCE_NO,
  UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(OB.BITS,32000,1)) VARCHAR_BITS,
  LENGTH(UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(OB.BITS,32000,1))) VARCHAR_BITS_LENGTH,
  BITS_LENGTH
FROM
  OPERATION_BINARY OB
WHERE
  TYPE='D';

Let’s assume that you have an engineering master that you want to use as the source that will be appended to various work order operations.  You could use the view like this to see the source data:

SELECT
  VARCHAR_BITS,
  VARCHAR_BITS_LENGTH,
  BITS_LENGTH
FROM
  OPERATION_BINARY_VIEW
WHERE
  WORKORDER_TYPE='M'
  AND WORKORDER_BASE_ID='0011881110'
  AND WORKORDER_LOT_ID='00'
  AND WORKORDER_SPLIT_ID='0'
  AND WORKORDER_SUB_ID='0'
  AND SEQUENCE_NO=10;

VARCHAR_BITS                    VARCHAR_BITS_LENGTH BITS_LENGTH
------------------------------- ------------------- -----------
TACK AND WELD COMPLETE PER B\P.                  31          32

In the above, note that the VARCHAR_BITS column value is one less than the value in the BITS_LENGTH column – the BITS_LENGTH column value includes the CHR(0) at the end of the column data, while the other length does not.  Now, let’s find a victim series of work order lots:

SELECT
  VARCHAR_BITS,
  VARCHAR_BITS_LENGTH,
  BITS_LENGTH,
  WORKORDER_LOT_ID
FROM
  OPERATION_BINARY_VIEW
WHERE
  WORKORDER_TYPE='W'
  AND WORKORDER_BASE_ID='11111'
  AND WORKORDER_SPLIT_ID='0'
  AND WORKORDER_SUB_ID='0'
  AND SEQUENCE_NO=10
ORDER BY
  WORKORDER_LOT_ID;

VARCHAR_BITS                                    VARCHAR_BITS_LENGTH BITS_LENGTH WO
----------------------------------------------- ------------------- ----------- --
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 1
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 10
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 11
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 12
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 13
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 14
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 15
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 16
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 2
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 3
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 4
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 5
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 6
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 7
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 8
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT.                 48          49 9

There are 16 lots in this work order base ID, so let’s create an UPDATE statement to append our source engineering master’s operation long description to the long description for these lots:

UPDATE
  OPERATION_BINARY OB2
SET
  (BITS,BITS_LENGTH) =
  (SELECT
    UTL_RAW.CAST_TO_RAW(
      UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(OB2.BITS,32000,1))
      || ' ' || SOURCE_OB.VARCHAR_BITS || CHR(0)
      ) NEW_BITS,
    OB2.BITS_LENGTH+SOURCE_OB.VARCHAR_BITS_LENGTH+2 NEW_BITS_LENGTH
  FROM
    OPERATION_BINARY_VIEW SOURCE_OB
  WHERE
    SOURCE_OB.WORKORDER_TYPE='M'
    AND SOURCE_OB.WORKORDER_BASE_ID='0011881110'
    AND SOURCE_OB.WORKORDER_LOT_ID='00'
    AND SOURCE_OB.WORKORDER_SPLIT_ID='0'
    AND SOURCE_OB.WORKORDER_SUB_ID='0'
    AND SOURCE_OB.SEQUENCE_NO=10)
WHERE
  OB2.TYPE='D'
  AND OB2.WORKORDER_TYPE='W'
  AND OB2.WORKORDER_BASE_ID='11111'
  AND OB2.WORKORDER_SPLIT_ID='0'
  AND OB2.WORKORDER_SUB_ID='0'
  AND OB2.SEQUENCE_NO=10;

16 rows updated.

The inline view in the above combines the source OPERATION_BINARY long description with the long description (aliased as OB2) from the row that will be updated in the OPERATION_BINARY table.  The inline view also calculates the new value for the BITS_LENGTH column – I initially thought that it should be +1, not +2, but the following step shows that it should be +2 so that BITS_LENGTH is one greater than VARCHAR_BITS_LENGTH.  Now, we can check the outcome of the append:

SELECT
  VARCHAR_BITS,
  VARCHAR_BITS_LENGTH,
  BITS_LENGTH,
  WORKORDER_LOT_ID
FROM
  OPERATION_BINARY_VIEW
WHERE
  WORKORDER_TYPE='W'
  AND WORKORDER_BASE_ID='11111'
  AND WORKORDER_SPLIT_ID='0'
  AND WORKORDER_SUB_ID='0'
  AND SEQUENCE_NO=10
ORDER BY
  WORKORDER_LOT_ID;

VARCHAR_BITS                                                                    VARCHAR_BITS_LENGTH BITS_LENGTH WO
------------------------------------------------------------------------------- ------------------- ----------- --
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 1
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 10
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 11
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 12
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 13
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 14
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 15
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 16
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 2
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 3
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 4
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 5
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 6
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 7
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 8
USE PICK OFF FIXTURE TO TACK IN PLACE PER PRINT. TACK AND WELD COMPLETE PER B\P.                 81          82 9

If we are satisfied with the results, we can issue a COMMIT and check the result in the ERP system.  TEST, TEST, TEST before using the above in production.

Great, but not quite there yet – the OP further refined the question.  He would like to combine the text stored in the BLOBs for SEQUENCE_NO 10, 20, and 30 with the text stored in the BLOB for SEQUENCE_NO 40, and store those results in the row for SEQUENCE_NO 40, for any given combination of WORKORDER_TYPE, WORKORDER_BASE_ID, WORKORDER_SPLIT_ID, and WORKORDER_SUB_ID.   Additionally, the OP would like to be able to supply a list of WORKORDER_TYPE, WORKORDER_BASE_ID, WORKORDER_SPLIT_ID, and WORKORDER_SUB_ID to which this combining operation will be performed.

We have now progressed from difficult to impossible (well, almost impossible).  Here is what I suggested:

You want to automatically fold the long descriptions stored in BLOB columns for the first three operations into the fourth operation, and you want to be able to do it without repeatedly executing update statements.  WARNING – do not try this unless you understand what is happening and how to verify that the correct change is made before issuing the final COMMIT.  First, the view from the previous solution will be used:

CREATE OR REPLACE VIEW OPERATION_BINARY_VIEW AS
SELECT
  WORKORDER_TYPE,
  WORKORDER_BASE_ID,
  WORKORDER_LOT_ID,
  WORKORDER_SPLIT_ID,
  WORKORDER_SUB_ID,
  SEQUENCE_NO,
  UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(OB.BITS,32000,1)) VARCHAR_BITS,
  LENGTH(UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(OB.BITS,32000,1))) VARCHAR_BITS_LENGTH,
  BITS_LENGTH
FROM
  OPERATION_BINARY OB
WHERE
  TYPE='D';

Next, we need a temporary holding table for the work orders and sub IDs you would like to modify.  Create the following table:

CREATE TABLE TEMP_WO_LIST AS
SELECT
  WORKORDER_TYPE,
  WORKORDER_BASE_ID,
  WORKORDER_LOT_ID,
  WORKORDER_SPLIT_ID,
  WORKORDER_SUB_ID
FROM
  OPERATION
WHERE
  1=2;

I created a dummy engineering master and then created 3 work orders from the master: 89890/1 through 89890/3.  I then modified the engineering master and created work order 89891/1.  These are the work orders I want to fix, like you are wanting to fix (actually, just Sub 0).  I insert the rows into the temporary holding table:

INSERT INTO TEMP_WO_LIST VALUES ('W','89890','1','0','0');
INSERT INTO TEMP_WO_LIST VALUES ('W','89890','2','0','0');
INSERT INTO TEMP_WO_LIST VALUES ('W','89890','3','0','0');
INSERT INTO TEMP_WO_LIST VALUES ('W','89891','1','0','0');

COMMIT;

OK, there are now 4 work orders listed in the temporary holding table, let’s see the long descriptions for the operations:

SELECT
  OBV.WORKORDER_BASE_ID,
  OBV.WORKORDER_LOT_ID,
  OBV.WORKORDER_SUB_ID,
  OBV.SEQUENCE_NO,
  OBV.VARCHAR_BITS
FROM 
  OPERATION_BINARY_VIEW OBV,
  TEMP_WO_LIST TWL
WHERE
  TWL.WORKORDER_TYPE=OBV.WORKORDER_TYPE
  AND TWL.WORKORDER_BASE_ID=OBV.WORKORDER_BASE_ID
  AND TWL.WORKORDER_LOT_ID=OBV.WORKORDER_LOT_ID
  AND TWL.WORKORDER_SPLIT_ID=OBV.WORKORDER_SPLIT_ID
  AND TWL.WORKORDER_SUB_ID=OBV.WORKORDER_SUB_ID
  AND OBV.SEQUENCE_NO IN (10,20,30);

WORKORDER_ WOR WOR SEQUENCE_NO VARCHAR_BITS
---------- --- --- ----------- ------------
89890      1   0            10 OP 10 DESC
89890      1   0            20 OP 20 DESC
89890      1   0            30 OP 30 DESC
89890      2   0            10 OP 10 DESC
89890      2   0            20 OP 20 DESC
89890      2   0            30 OP 30 DESC
89890      3   0            10 OP 10 DESC
89890      3   0            20 OP 20 DESC
89890      3   0            30 OP 30 DESC
89891      1   0            10 OP 10 DESC2
89891      1   0            20 OP 20 DESC2
89891      1   0            30 OP 30 DESC2

Notice in work order 89891/1 I added the number 2 to the end of each long description when I modified the engineering master before creating that work order.
 
Next, we need to use a trick to collapse 3 rows into a single row so that the long descriptions may be combined.  This may be done by using MAX and DECODE with a GROUP BY clause like this (note that I told SQL*Plus to line-wrap the column – just ignore that):

SELECT
  OBV.WORKORDER_BASE_ID,
  OBV.WORKORDER_LOT_ID,
  OBV.WORKORDER_SUB_ID,
  MAX(DECODE(OBV.SEQUENCE_NO,10,OBV.VARCHAR_BITS||' ')) ||
  MAX(DECODE(OBV.SEQUENCE_NO,20,OBV.VARCHAR_BITS||' ')) ||
  MAX(DECODE(OBV.SEQUENCE_NO,30,OBV.VARCHAR_BITS)) VARCHAR_BITS,
  NVL(MAX(DECODE(OBV.SEQUENCE_NO,10,OBV.VARCHAR_BITS_LENGTH))+1,0) +
  NVL(MAX(DECODE(OBV.SEQUENCE_NO,20,OBV.VARCHAR_BITS_LENGTH))+1,0) +
  NVL(MAX(DECODE(OBV.SEQUENCE_NO,30,OBV.VARCHAR_BITS_LENGTH)),0) VARCHAR_LENGTH
FROM 
  OPERATION_BINARY_VIEW OBV,
  TEMP_WO_LIST TWL
WHERE
  TWL.WORKORDER_TYPE=OBV.WORKORDER_TYPE
  AND TWL.WORKORDER_BASE_ID=OBV.WORKORDER_BASE_ID
  AND TWL.WORKORDER_LOT_ID=OBV.WORKORDER_LOT_ID
  AND TWL.WORKORDER_SPLIT_ID=OBV.WORKORDER_SPLIT_ID
  AND TWL.WORKORDER_SUB_ID=OBV.WORKORDER_SUB_ID
  AND OBV.SEQUENCE_NO IN (10,20,30)
GROUP BY
  OBV.WORKORDER_BASE_ID,
  OBV.WORKORDER_LOT_ID,
  OBV.WORKORDER_SPLIT_ID,
  OBV.WORKORDER_SUB_ID;

WORKORDER_ WOR WOR VARCHAR_BITS         VARCHAR_LENGTH
---------- --- --- -------------------- --------------
89890      1   0   OP 10 DESC OP 20 DES             32
                   C OP 30 DESC
89890      2   0   OP 10 DESC OP 20 DES             32
                   C OP 30 DESC
89890      3   0   OP 10 DESC OP 20 DES             32
                   C OP 30 DESC
89891      1   0   OP 10 DESC2 OP 20 DE             35
                   SC2 OP 30 DESC2

Now, like the final solution earlier, we convert the above into an UPDATE statement:

UPDATE
  OPERATION_BINARY OB2
SET
  (BITS,BITS_LENGTH)=
  (SELECT
   UTL_RAW.CAST_TO_RAW(
    MAX(DECODE(OBV.SEQUENCE_NO,10,OBV.VARCHAR_BITS||' ')) ||
    MAX(DECODE(OBV.SEQUENCE_NO,20,OBV.VARCHAR_BITS||' ')) ||
    MAX(DECODE(OBV.SEQUENCE_NO,30,OBV.VARCHAR_BITS||' ')) ||
    UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(OB2.BITS,32000,1)) || CHR(0) ) VARCHAR_BITS,
    NVL(MAX(DECODE(OBV.SEQUENCE_NO,10,OBV.VARCHAR_BITS_LENGTH))+1,0) +
    NVL(MAX(DECODE(OBV.SEQUENCE_NO,20,OBV.VARCHAR_BITS_LENGTH))+1,0) +
    NVL(MAX(DECODE(OBV.SEQUENCE_NO,30,OBV.VARCHAR_BITS_LENGTH))+1,0) +
    OB2.BITS_LENGTH + 1 VARCHAR_LENGTH
  FROM 
    OPERATION_BINARY_VIEW OBV,
    TEMP_WO_LIST TWL
  WHERE
    TWL.WORKORDER_TYPE=OBV.WORKORDER_TYPE
    AND TWL.WORKORDER_BASE_ID=OBV.WORKORDER_BASE_ID
    AND TWL.WORKORDER_LOT_ID=OBV.WORKORDER_LOT_ID
    AND TWL.WORKORDER_SPLIT_ID=OBV.WORKORDER_SPLIT_ID
    AND TWL.WORKORDER_SUB_ID=OBV.WORKORDER_SUB_ID
    AND OBV.SEQUENCE_NO IN (10,20,30)
    AND OBV.WORKORDER_TYPE=OB2.WORKORDER_TYPE
    AND OBV.WORKORDER_BASE_ID=OB2.WORKORDER_BASE_ID
    AND OBV.WORKORDER_LOT_ID=OB2.WORKORDER_LOT_ID
    AND OBV.WORKORDER_SUB_ID=OB2.WORKORDER_SUB_ID
  GROUP BY
    OBV.WORKORDER_BASE_ID,
    OBV.WORKORDER_LOT_ID,
    OBV.WORKORDER_SPLIT_ID,
    OBV.WORKORDER_SUB_ID)
WHERE
  (WORKORDER_TYPE,
   WORKORDER_BASE_ID,
   WORKORDER_LOT_ID,
   WORKORDER_SPLIT_ID,
   WORKORDER_SUB_ID) IN
  (SELECT
    WORKORDER_TYPE,
    WORKORDER_BASE_ID,
    WORKORDER_LOT_ID,
    WORKORDER_SPLIT_ID,
    WORKORDER_SUB_ID
  FROM
    TEMP_WO_LIST)
  AND OB2.SEQUENCE_NO=40;

4 rows updated. 

Then we verify that we obtained the desired results:

SELECT
  OBV.WORKORDER_BASE_ID,
  OBV.WORKORDER_LOT_ID,
  OBV.SEQUENCE_NO SEQ,
  OBV.VARCHAR_BITS,
  OBV.VARCHAR_BITS_LENGTH VB_LENGTH,
  OBV.BITS_LENGTH B_LENGTH
FROM
  OPERATION_BINARY_VIEW OBV,
  TEMP_WO_LIST TWL
WHERE
  TWL.WORKORDER_TYPE=OBV.WORKORDER_TYPE
  AND TWL.WORKORDER_BASE_ID=OBV.WORKORDER_BASE_ID
  AND TWL.WORKORDER_LOT_ID=OBV.WORKORDER_LOT_ID
  AND TWL.WORKORDER_SPLIT_ID=OBV.WORKORDER_SPLIT_ID
  AND TWL.WORKORDER_SUB_ID=OBV.WORKORDER_SUB_ID
ORDER BY
  OBV.WORKORDER_BASE_ID,
  OBV.WORKORDER_LOT_ID,
  OBV.SEQUENCE_NO;

WORKORDER_ WOR SEQ VARCHAR_BITS          VB_LENGTH   B_LENGTH
---------- --- --- -------------------- ---------- ----------
89890      1    10 OP 10 DESC                   10         11
89890      1    20 OP 20 DESC                   10         11
89890      1    30 OP 30 DESC                   10         11
89890      1    40 OP 10 DESC OP 20 DES         44         45
                   C OP 30 DESC OP 40 D
                   ESC
89890      2    10 OP 10 DESC                   10         11
89890      2    20 OP 20 DESC                   10         11
89890      2    30 OP 30 DESC                   10         11
89890      2    40 OP 10 DESC OP 20 DES         44         45
                   C OP 30 DESC OP 40 D
                   ESC
89890      3    10 OP 10 DESC                   10         11
89890      3    20 OP 20 DESC                   10         11
89890      3    30 OP 30 DESC                   10         11
89890      3    40 OP 10 DESC OP 20 DES         44         45
                   C OP 30 DESC OP 40 D
                   ESC
89891      1    10 OP 10 DESC2                  11         12
89891      1    20 OP 20 DESC2                  11         12
89891      1    30 OP 30 DESC2                  11         12
89891      1    40 OP 10 DESC2 OP 20 DE         48         49
                   SC2 OP 30 DESC2 OP 4
                   0 DESC2

In the above, B_LENGTH must be 1 greater than VB_LENGTH.  If we are happy, we can issue a COMMIT.





“Expert Oracle Practices: Oracle Database Administration from the Oak Table” Book

1 01 2010

November 30, 2009 (Follow-up Jan 1, 3, 4, 6, 8, 9, 12, 15, 20, 23, 28, Feb 12, 14, 16, Mar 21, May 7, Dec 28 2010)

I had the great pleasure of co-writing with Randolf Geist (http://oracle-randolf.blogspot.com/) two (very long) chapters for the “Expert Oracle Practices: Oracle Database Administration from the Oak Table” book.  The chapter titles are “Understanding Performance Optimization Methods” and “Choosing a Performance Optimization Method”. The two chapters explore the vast number of ways to identify Oracle database performance problems, and demonstrate methods of attacking the problems once identified. Some of the techniques demonstrated in the chapters have not previously appeared in book form. The chapters also attempt to address some of the bad advice found when performing web searches on Oracle keywords. This blog, and that of Randolf Geist, provide a good idea of what our two chapters in the book cover – essentially everything from “guessing” based on what one would find through a Google search, to trying to use the buffer cache hit ratio, all the way to reading process stack dumps (and quite a number of other things in between). Several reproducible test cases are included in the chapters, with example output from Oracle 10.2.0.4, 11.1.0.6, 11.1.0.7, and 11.2.0.1.

The book description on the Apress website as well as Amazon seems to be a bit limited compared to the book’s table of contents, although the authors’ descriptions are thorough. Below is the table of contents for the book:

Book Contents

I DBA Fundamentals
1 Battle Against Any Guess – Alex Gorbachev
2 A Partly-cloudy Future – Jeremiah Wilton
3 Developing A Performance Optimization Method – Connie Green, Graham Wood, Uri Shaft
4 The DBA as Designer – Melanie Caffrey

II Network and Operating Systems
5 Running Oracle on Windows – Niall Litchfield

III SQL and PL/SQL
6 Managing SQL Performance – Karen Morton
7 PL/SQL and the CBO – Joze Senegacnik

IV Performance Optimization Methods
8 Understanding Performance Optimization Methods – Charles Hooper & Randolf Geist
9 Choosing a Performance Optimization Method – Randolf Geist & Charles Hooper

IV Operational Concerns
10 Managing the Very Large Database – Tim Gorman
11 Statistics Collection and Creation – Jonathan Lewis

V Troubleshooting
12 Troubleshooting Latch Contention – Riyaj Shamsudeen
13 Measuring for Robust Performance – Robyn Sands

VI Security
14 Securing Users – Pete Finnigan
15 Securing Data – Pete Finnigan

May 7, 2010:
An independent review of the Expert Oracle Practices book appeared in the May 2010 edition of the NoCOUG Journal, written by Dave Abercrombie (the same reviewer posted a short review on Amazon’s website).  The review is thorough, and covers items that were missed in my brief chapter summaries that are below.  Most of chapter 1, as well as an interview with the author of chapter 1, also appeared in the NoCOUG Journal.

http://www.amazon.com/Expert-Oracle-Practices-Database-Administration/dp/1430226684

Late follow-up (January 1, 2010):
I ordered a copy of the “Expert Oracle Practices: Oracle Database Administration from the Oak Table” book from Amazon in the middle of November.  Today I bought an “Alpha” copy of the book from the Apress site so that I would have an opportunity to read the other chapters in the book (and so that I could have an electronic copy of the completed book).  The “Alpha” copy of the book is a formatting mine-field – the victim of Microsoft Word “auto-correcting” document styles, missing mono-spaced fonts in the code sections, and yellow highlighter all over a couple of the chapters (there is a reason for the yellow highlighter all over our two chapters – the cyan highlighter was too hard on the eyes).  The “Alpha” copy, at least for the two chapters that Randolf and I wrote, was captured just before we had an opportunity to address the concerns of the second editor (correction – the technical reviewer had made comments previously, the “Alpha” copy was captured before we had an opportunity to address the concerns of the first editor of the chapters – portions of the chapters were modified heavily based on his feedback) – there were three more rounds of technical edits after those concerns were addressed.  Don’t let the rough edges of the “Alpha” copy color your opinion of the finished product.

Late follow-up (Jan 3-28, Feb 12, 14, 16, Mar 21 2010):
Below are descriptions of each chapter of the book.

Chapter 1 (Final Version) – Alex Gorbachev, the founder of Battle Against Any Guess (BAAG Party), identifies examples of attempted problem solving through guess work, throwing the book at the problem, and shooting the problem with silver bullets – as the attempted guess work is suggested in various online forums.  He seems to have a strong bias against guessing and a bias for understanding the actual source of the problem.  In the chapter he attempts to answer the question of why the database is slow every Monday when it rains, but does not address the issue of why another database runs slowly when someone yells at the SAN (there was a serious YouTube video floating around a couple of months ago that demonstrated verbally abusing the SAN reduced its performance).  An interesting issue is raised in the chapter – is not (blindly) implementing a best practice actually a best practice – and is that advice then a circular reference that should be followed, or not followed?

Chapter 2 (Final Version) – Jeremiah Wilton, Amazon.com’s first DBA, provides insight into the meaning of cloud computing, describes the Software as a Service, Platform as a Service, and Infrastructure as a Service variants.  Jeremiah also describes the capabilities of several cloud computing providers, and describes in detail how to set up Amazon.com’s EC2.

Chapter 3 (Final Version) – Connie Green, Graham Wood, and Uri Shaft (you will find two of their names in the spdoc.txt file in the rdbms/admin directory) – some of the developers of the performance tuning and monitoring features in Oracle Database, describe the philosophy behind the development of a sound performance optimization method, debunk mysteries and black magic, and clean up after silver bullets miss their target. The chapter lists five steps to developing a time-based optimization method.  Describes when to use Enterprise Manager, Automatic Database Diagnostic Monitor (extended description), Active Session History (extended description), Automatic Workload Repository/Statspack reports (extended description), and SQL trace.  The final version of the chapter extends beyond the philosophy stage – if the performance is not the same as it was yesterday, something must have changed.  What is the scope of the problem: specific SQL statement, instance wide, or something else?  The section covering the usage of the ASH (Active Session History) data to pinpoint the starting point of performance problems seems to have been extended.  The chapter also describes what the DB time statistic really indicates, and how may it be used for troubleshooting.

Chapter 4 (Final Version) – Melanie Caffrey, Senior Development Manager in the Enterprise Linux group at Oracle, describes the philosophy of being an all-around fantastic, energetic, proactive DBA with the ultimate goal of having your evenings and weekends rarely interrupted by various database problems.  This chapter is not a simple bulleted list of what to do and what not to do – the topics include:

  • How a DBA should develop a partnership with the developers to reduce the chances of performance problems when new applications are released for production use.
  • Difference between Design Upfront/Develop Later/Waterfall approach, the Agile approach, and the Cowboy approach to development.
  • Choosing the correct data type with an example of what could happen when defining character columns as VARCHAR2(4000), potential problems when comparing CHAR columns with VARCHAR2 columns, and cardinality calculation problems when numbers are stored in VARCHAR2 columns.
  • Points against database independent design.
  • Building integrity and other logic into the database, rather than at the application level.
  • Preventing the Oracle database from appearing as a black box for dumping data.
  • Recommended Oracle documentation to read.
  • Learning from your mistakes.
  • Leveraging the Oracle features that are part of the database package – getting your money’s worth from this expensive program while improving performance.

Chapter 5 (Final Version) – Niall Litchfield answers the question: Is Windows a serious platform for Oracle databases?  And if so, what does a Unix DBA/administrator need to know about running Oracle on the Windows platform.  Architecture differences on Windows and Unix-like operating systems (CPU, Memory, Storage, Network) are described.  Using SysInternal’s (Microsoft’s) Process Monitor to drill into Oracle activity for a single session, an overview of the Windows registry, and the Windows Scripting Host are also described.

Chapter 6 (Final Version) – Karen Morton states quite simply to begin at the beginning when trying to control SQL performance.  The author of the SQL in the application must repeat: “I am responsible for the performance of the code I write or maintain.”  The chapter explores some of the differences between EXPLAIN PLAN output and that of DBMS_XPLAN.DISPLAY_CURSOR.  The chapter also provides an introduction to extended SQL traces (event 10046), and why extended SQL traces are better than execution plans generated by DBMS_XPLAN.DISPLAY_CURSOR.  The chapter includes the investigation of a couple SQL performance problems – the case studies show how to detect that a problem is present and provide ideas for rectifying the problem: lack of a good index, skewed data, and SQL that should be rewritten because it is projected to perform poorly as the data volume increases.  (The Final Version of the chapter provides an additional case study that demonstrates the effects of calling a PL/SQL function from SQL, shows that the timings in DBMS_XPLANs may be deceptive when such functions are called, and shows how to determine the actual impact of context switches caused by combining SQL with PL/SQL.)

Chapter 7 (Final Version) – Joze Senegacnik explains the stages of a SQL statement hard parse; defines selectivity, cardinality, and cost; then unravels the mysteries of bad execution plans caused by PL/SQL functions that are present in the WHERE clause – and as a bonus explains how to correct such problems.

Chapter 8 (Final Version) – Charles Hooper and Randolf Geist explain that there is so much instrumentation (to help optimization) in an Oracle RDBMS that it is difficult to determine which instrumentation method is most appropriate, especially if the available instrumentation methods are not well understood.  This chapter attempts to cover a large number of the instrumentation and optimization methods including:

  • Blindly changing system parameters based solely on information found on the Internet or books.
  • Monitoring and reacting to the buffer cache hit ratio, or another similar ratio.
  • Monitoring the delta (change) values of system and session statistics (V$SYSSTAT, V$SESSTAT, etc.).
  • Monitoring file related activity (V$FILESTAT, V$TEMPSTAT, V$SEGSTAT, various system and session statistics, etc.).
  • Monitoring the delta values of system and session wait events (V$SYSTEM_EVENT, V$SESSION_EVENT, V$SESSION_WAIT, etc.).
  • Monitoring the CPU utilization (CPU used by this session system and session statistics, statistics from V$OSSTAT, statistics from V$SYS_TIME_MODEL and V$SESS_TIME_MODEL, etc.).
  • Capturing Statspack snapshots and creating Statspack or AWR reports.
  • Monitoring delta values of the statistics associated with each SQL statement in the library cache (CPU_TIME, ELAPSED_TIME, FETCHES, EXECUTIONS, BUFFER_GETS, DISK_READS, etc. in V$SQL).
  • Examining plan statistics and changes in execution plans for SQL statements (V$SQL_PLAN_STATISTICS_ALL, V$SQL_WORKAREA_ACTIVE, V$SQL_SHARED_CURSOR, V$SQL_PLAN, DBMS_XPLAN.DISPLAY_CURSOR).
  • Examining system and session level optimizer parameters affecting calculations performed while generating execution plans with the lowest calculated cost (V$SYS_OPTIMIZER_ENV, V$SES_OPTIMIZER_ENV).
  • Generating 10053 cost based optimizer traces during hard parses in order to determine the calculated reason for an execution plan.
  • Generating 10046 extended SQL traces at levels 4, 8, or 12 during the execution of individual SQL statements as well of normal application processes in order to determine specific causes of performance problems within critical application procedures.
  • Examining Oracle database server stack traces.
  • Reviewing the ADDM findings (with appropriate Enterprise Edition license and extra cost license options).
  • Examining network packets and associated statistics (tcpdump, Wireshark/Ethereal, etc.).
  • Examining client-side traces (sqlnet, Process Monitor, Spy++, etc.)
  • Investigating enqueue type waits
  • (And probably a couple other things along the way.)

Additionally, a couple of interesting test cases are introduced:

  • Selecting 0.06% of the rows from a table (containing 100,000,000 rows) completes faster using a full table scan instead of an index range scan.
  • Generating a deadlock on Oracle 11.1.0.6 through 11.2.0.1, while the exact same sequence of events fails to trigger a deadlock on Oracle 10.2.0.4.
  • Executing a query using bind variables in a single session causes more than 1,024 child cursors to be created, resulting in multiple children with the same SQL_ID and CHILD_NUMBER to be created when the SQL statement is executed with 10,000 different sets of bind variable values.
  • Head to head comparison of direct I/O compared to operating system buffered I/O with Oracle 11.2.0.1 during one of the test cases (and some of the potential pitfalls of each approach).
  • What happens when a CPU load is applied inside and outside the database instance.
  • TKPROF in 11.1.0.6 and above potentially shows the wrong Row Source Execution Plan when the same SQL statement is executed multiple times while tracing is enabled.

(Note that through the final proofing stages of the chapters, Chapter 8 was actually Chapter 9, and Chapter 9 was actually Chapter 10.  The opening section for Chapter 8 in the final version of the book states the following: “Chapter 10, which follows next, provides a decision-making process that we believe will help you apply the right method to the right problem.”  The same problem is present in Chapter 9 when it refers back to the concepts introduced in the previous chapter – Chapter 9 states to look at Chapter 9.  If you read the words “Chapter 10”, please interpret that as “Chapter 9”.  If you read the words “Chapter 9”, please interpret that as “Chapter 8”.  A similar problem appears in Chapter 6, which refers to Listing 7-1, Listing 7-2, etc.)
(I just caught a typo in the script at the top of page 186 – I am sure that this typo was corrected in one of the 25-30 edits of our chapters, but that change was somehow lost and not noticed in the final series of edits.  The script shows this “AND SS.OBJ#=DO.DATA_OBJECT_ID AND SS.DATAOBJ#=DO.DATA_OBJECT_ID” – it was supposed to show this “AND SS.OBJ#=DO.OBJECT_ID AND SS.DATAOBJ#=DO.DATA_OBJECT_ID”.  Another typo is found on line 8 in the script on page 218 – change the second TE.DISK_READS to TS.DISK_READS.)
(Mar 21, 2010: The SQL statement on page 188 should have included the following in the WHERE clause to reduce the chances of repeated rows appearing in the output, see the March 22, 2010 blog article for an explanation: AND S.SQL_CHILD_NUMBER=SQL.CHILD_NUMBER – ideally, the query would return the S.SQL_CHILD_NUMBER column also.)

Chapter 9 (Final Version) – Randolf Geist and Charles Hooper use real-world examples to demonstrate when to use the various monitoring methods in pursuit of improved performance, switching between monitoring methods as needed.  The chapter wraps up with various techniques to improve performance based on the data collected during database monitoring.

Chapter 10 (Final Version) – Tim Gorman describes what needs to be considered when managing very large databases.  His chapter is about those massive databases that don’t quite fit onto the 12 drive RAID 10 floppy drive array.  The databases of this variety are huge, growing at a rate of 25TB to 50TB per day.    He describes the raw throughput required to hit this level of growth – it is not a write rate of 600MB/s that is required, but instead one of roughly 10GB/s (OK, how many EXADATA V2 units were required to maintain this write level?). The chapter opens with a story about Tim accidentally dropping a 15TB index on a 45TB table, and learning from one’s mistakes.  And we later learn that “the fastest mass update or delete is actually an insert.”  He describes some of the advantages of using the extra cost partitioning option, and ASM, and also describes some of the challenges of backing up massive databases – at a backup rate of 1,000GB per hour, that 5PB database will be backed up in no time (no time soon – 200 days by Tim’s calculations).  After reading the chapter, I just have one question – is all of that data being generated just to prove that Oracle Databases are able to handle it, or is someone actually reading/using all of the data?

Chapter 11 (Final Version) – Jonathan Lewis attacks the subject of statistics.  This is not a simple chapter that shows how to collect table, index, and system statistics.  It is instead a chapter that exposes some of the problems that cause the cost-based optimizer to generate seemingly silly/stupid execution plans.  Is it caused by a bug, or is there a reason for the execution plan – and if there is a reason, is there a way to identify the source and correct the problem without adding multiple hints to every SQL statement?  In the chapter Jonathan offers a couple of clever, seemingly simple questions, for which most people will initially answer incorrectly – this is done as a demonstration of what potentially trips up the cost-based optimizer.  He also demonstrates how the optimizer could become flustered by the way the index clustering factor is calculated, how both dynamic sampling and  11g’s extended column statistics may be ineffective in certain cases, and how the nightly automatic statistics gathering job could cause problems for partitioned tables.  Also discussed is how a histogram’s impact on cardinality estimates changes when upgrading from Oracle 10.2.0.3 to 10.2.0.4 or 11g.  After reading the chapter you will be aware that to the optimizer, all possible outcomes of an inequality totals 11% of all rows in a row source, where is that proof that 1=2 when you need it?

Chapter 12 (Final Version) – Riyaj Shamsudeen drives into the details of latch contention – this is not a chapter that should be read unless you are fully awake – I think that I might need to re-read the chapter a couple of more times to fully understand what was presented.  The chapter begins with a brief description of latches and how latches differ from enqueues.  Identifying and finding the cause for various types of latch contention is demonstrated through numerous SQL statements that hit Oracle’s various fixed tables and performance views, and he even shows a couple of examples of using TKPROF’s ORADEBUG to dump memory contents for analysis.  The 0 key on the keyword might have been stuck when Riyaj described the SESSION_CACHED_CURSORS parameter.

Chapter 13 (Final Version) – Robyn Sands describes why it is important to measure twice, cut once, and then follow up with a re-measure every so often.  Whether it is a needle in the haystack, a silver rock mixed in with a bucket of sand, or an occasional database reporting problem hiding among normal activity, it is hard to know if you found a needle, a silver rock, or a database performance problem unless you periodically capture the measurements of the items in front of you.  After a brief introduction to what the expectations are of performance analysis, Robyn introduced topics that induced flashbacks of university-level linear algebra, statistics, and even political science while ripping through captured runtime performance statistics in search of the silver rocks (OK, I admit that they were red rocks) that indicate unexpected performance characteristics.

Chapter 14 (Final Version) – Pete Finnigan provides general information about securing database user accounts, stressing the need to eliminate those database user accounts that are installed by default but not used, with the goal of reducing the attack surface.  Pete also introduces several of the scripts that he hosts on his website: use.sql (assess built-in and customer installed user account permissions), find_all_privs.sql (determine the assigned privileges of a user account), cracker-v2.0 (you better hope that access to SYS.USER$ is restricted), and profiles.sql (retrieve the security settings for the user profiles configured in the database).  The author also provides several SQL statements throughout the chapter that may be used for analyzing various security settings.  The detailed description of Oracle’s one way password hashing algorithm is interesting.

Chapter 15 (Final Version) – Pete Finnigan advises “learn to think like a hacker” in this chapter that describes the difficulties of securing data in the database.  Pete introduces a couple more scripts from his website while peeling away the layers of security permissions that permit access to credit card (or other similar data) stored within the database.  He introduces several sources for additional security permissions for seeing the “secured” data – you better hope that bind variables are in use, and that people do not know how to view the bind variables submitted by other sessions.  The author mentions that database security extends to the whole server – don’t leave files containing unencrypted versions of the data stored in files in the filesystem of the server (and if you read chapter 14, you probably have already made up your mind to take down the post-it notes with the DBA passwords).  Auditing is briefly mentioned.

January 4, 2010:
The Final Version of the Electronic Book may be downloaded from Apress Now – if you bought the “Alpha” copy of the book, you should be able to download the final version for free.  The PDF is 593 pages including the front cover and index.

January 20, 2010:
I received notification that the printed version of the book shipped from Amazon on January 17, and should arrive by postal mail today.

January 23, 2010:
It appears that the first 100 pages of the book may be viewed on the Google Books site.

February 12, 2010:
Having now read through all of the chapters in the book, at the end of January the book authors discussed the description of the book, as currently listed on Amazon and the Apress site, and determined that it is inaccurate.  First, the page count in not 400, but actually about 532 pages when excluding the index at the back of the book.  Additionally, the bullet points under the heading “What you’ll learn” do not best describe what is in the printed book – those bullets are currently listed as follows:

  • Eliminate guesswork by attacking database problems in a systematic manner.
  • Manage transactional performance in SQL and PL/SQL.
  • Secure your database from outside attack.
  • Effectively deal with operating system platforms such as Windows and Linux.
  • Diagnose database crashes.
  • Choose the right block size to optimize I/O throughput.

I received notification today that Apress will be updating the description of the book contents based on feedback from the book authors.  Expect to see the bullet points changed to something similar to the following, hopefully by Monday:

  • Adopt a rational approach to database management
  • Add value to your organization as a database professional
  • Utilize effective techniques for performance management and optimization
  • Effectively exploit different platform technologies
  • Effectively secure your organization’s data
  • Gain a deeper understanding of database internal features and structures

May 7, 2010
Review of the book appeared in the
May 2010 edition of the NoCOUG Journal along with most of chapter 1 of the book.

December 28, 2010
It appears that the scripts for chapter 8 of the book are well hidden.  The scripts may be downloaded from the Apress website.  Inside the Expert_Oracle_Practices_Oracle_Database_Administration_from_the_Oak_Table-4524.zip download file is a folder titled ch08n09_UnderstandingOptMethods, and inside that folder is another folder titled ch09Scripts – this folder contains all of the scripts for chapters 8 and 9 (those scripts are listed at the end of chapter 8 in the book).

November 17, 2011
It appears that the script library file download for this book on the Apress website is corrupt.  You can download the script library from here: Expert_Oracle_Practices_Scripts.zip (save with a .zip extension, and not a .doc extension).





Excel – Graphical Scheduled Usage Viewer for Production Equipment

1 01 2010

January 1, 2010

This article is based on a sample included in a presentation that I conducted a couple of months ago.  Essentially, this example pulls utilization schedules from multiple equipment resources and generates both an equipment resource specific calendar and a graphical overview of the calendars for all equipment resources.  This demonstration uses ADO with a direct connection to an Oracle database for both scheduling the work orders that need to be processed at each of the equipment resources (using a very weak scheduling algorithm that does not go back to fill in unused time), and to create the graphical results in Excel.

The options area for the original Excel spreadsheet used in my presentation looked like this, but we will not create anything that complex in this article: 

– 

Before starting, we need to create source data for our example (note that some of the tables created are only needed for a short period of time, and may be dropped once all of the tables are created):

First, a statistic list of parts that will be manufactured:

CREATE TABLE
  T_PART_LIST AS
SELECT
  CAST(DBMS_RANDOM.STRING('U',15) AS VARCHAR2(30)) PART_ID,
  ROUND(DBMS_RANDOM.VALUE(1,30)) NUM_LOTS,
  ROUND(DBMS_RANDOM.VALUE(1,10)) NUM_OPERATIONS
FROM
  DUAL
CONNECT BY
  LEVEL<=100;

Next, the operation numbers and machine resources that will be used during the manufacture of each of the parts:

CREATE TABLE
  T_PART_OPERATION AS
SELECT
  PL.PART_ID,
  OP.RN*10 OPERATION_SEQ_NO,
  CAST(ROUND(DBMS_RANDOM.VALUE(0,10),2) AS NUMBER(22,2)) RUN_HOURS,
  CAST('MACHINE'||TO_CHAR(ROUND(DBMS_RANDOM.VALUE(1,100)),'000') AS VARCHAR2(15)) RESOURCE_ID
FROM
  T_PART_LIST PL,
  (SELECT
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=10) OP
WHERE
  OP.RN<=PL.NUM_OPERATIONS;

CREATE INDEX IND_T_PART_OP_PART_ID ON T_PART_OPERATION(PART_ID);

Next, the work order lots that will manufacture the parts:

CREATE TABLE
  T_WORKORDER_LIST AS
SELECT
  WO.WORKORDER_BASE_ID,
  CAST(LN.LOT_NUM AS VARCHAR2(3)) WORKORDER_LOT_ID,
  PL.PART_ID,
  TRUNC(SYSDATE+ROUND(DBMS_RANDOM.VALUE(0,400))) DESIRED_WANT_DATE
FROM
  (SELECT
    PART_ID,
    NUM_LOTS,
    ROWNUM RN
  FROM
    T_PART_LIST) PL,
  (SELECT
    CAST('W'||TO_CHAR(ROWNUM+10000) AS VARCHAR2(15)) WORKORDER_BASE_ID,
    ROWNUM RN
  FROM
    DUAL
  CONNECT BY
    LEVEL<=400) WO,
  (SELECT
    ROWNUM LOT_NUM
  FROM
    DUAL
  CONNECT BY
    LEVEL<=30) LN
WHERE
  PL.RN=WO.RN
  AND LN.LOT_NUM<=PL.NUM_LOTS
ORDER BY
  WO.WORKORDER_BASE_ID,
  LN.LOT_NUM;

Finally, a table that will eventually contain the schedules for the various machine resources:

CREATE TABLE T_RESOURCE_DAILY_SCH(
  RESOURCE_ID VARCHAR2(15),
  START_DATE DATE,
  FINISH_DATE DATE,
  RUN_HOURS NUMBER,
  PART_ID VARCHAR2(30),
  WORKORDER_BASE_ID VARCHAR2(15),
  WORKORDER_LOT_ID VARCHAR2(3),
  OPERATION_SEQ_NO NUMBER,
  DESIRED_WANT_DATE DATE,
  UNIT_ASSIGNED NUMBER);

CREATE INDEX IND_T_RESOURCE_DAILY_RES ON T_RESOURCE_DAILY_SCH(RESOURCE_ID);

We now switch to Excel to build the interface area with two ActiveX command buttons:

First, name the first worksheet as ShopCalendar  – this name is very important since it is referenced in the code for this example.  Next, add two ActiveX command buttons in the first two rows.  Give the first one the name cmdCreateSchedule, and the second one the name cmdDrawCalendar  – the names are not overly critical, but the names are referenced in the code.  Finally, double-click one of the buttons to access the Code Editor.

Delete any code that is shown in the Code Editor and add the following code:

Option Explicit

Dim dbDatabase As New ADODB.Connection 
Dim strDatabase As String
Dim strUserName As String
Dim strPassword As String
Private Function ConnectDatabase() As Integer
    Dim intResult As Integer

    On Error Resume Next

    If dbDatabase.State <> 1 Then
        'Connection to the database if closed
        strDatabase = "MyDB"
        strUserName = "MyUser"
        strPassword = "MyPassword"

        'Connect to the database
        'Oracle connection string
        dbDatabase.ConnectionString = "Provider=OraOLEDB.Oracle;Data Source=" & strDatabase & ";User ID=" & strUserName & ";Password=" & strPassword & ";ChunkSize=1000;FetchSize=100;"

        dbDatabase.ConnectionTimeout = 40
        dbDatabase.CursorLocation = adUseClient
        dbDatabase.Open

        If (dbDatabase.State <> 1) Or (Err <> 0) Then
            intResult = MsgBox("Could not connect to the database.  Check your user name and password." & vbCrLf & Error(Err), 16, "Excel Demo")

            ConnectDatabase = False
        Else
            ConnectDatabase = True
        End If
    Else
        ConnectDatabase = True
    End If
End Function

The above code is the basic logic needed to permit connecting to the Oracle database.  Next, add a reference to the ActiveX Data Objects.  Now we need to add the code for the scheduling routine – this code uses bind variables to minimize the number of hard parses:

Private Sub cmdCreateSchedule_Click()
    Dim intResult As Integer
    Dim strSQL As String
    Dim i As Integer
    Dim intResourceIndex As Integer      'Index that indicates the current resource ID index
    Dim strResourceID(100) As String     'Keep track of the new resource IDs read in
    Dim varResourceLastDate(100) As Date 'Keep track of the last date for the resource
    Dim strWorkOrderLast As String       'Used to make certain that the next operation starts after the previous
    Dim varWorkOrderLastDate As Date     'Used to make certain that the next operation starts after the previous
    Dim varScheduleStart As Date         'The first date in the schedule
    Dim snpData As ADODB.Recordset
    Dim comData As ADODB.Command

    intResult = ConnectDatabase
    If intResult = False Then
        Exit Sub
    End If

    'Set the starting point for all resources
    varScheduleStart = Now
    For i = 1 To 100
        strResourceID(i) = ""
        varResourceLastDate(i) = varScheduleStart
    Next i

    Set snpData = New ADODB.Recordset
    Set comData = New ADODB.Command

    'Retrieve the list of work order operations to be scheduled
    strSQL = "SELECT" & vbCrLf
    strSQL = strSQL & "  WO.WORKORDER_BASE_ID," & vbCrLf
    strSQL = strSQL & "  WO.WORKORDER_LOT_ID," & vbCrLf
    strSQL = strSQL & "  WO.PART_ID," & vbCrLf
    strSQL = strSQL & "  WO.DESIRED_WANT_DATE," & vbCrLf
    strSQL = strSQL & "  PO.OPERATION_SEQ_NO," & vbCrLf
    strSQL = strSQL & "  PO.RUN_HOURS," & vbCrLf
    strSQL = strSQL & "  PO.RESOURCE_ID" & vbCrLf
    strSQL = strSQL & "FROM" & vbCrLf
    strSQL = strSQL & "  T_WORKORDER_LIST WO," & vbCrLf
    strSQL = strSQL & "  T_PART_OPERATION PO" & vbCrLf
    strSQL = strSQL & "WHERE" & vbCrLf
    strSQL = strSQL & "  WO.PART_ID=PO.PART_ID" & vbCrLf
    strSQL = strSQL & "ORDER BY" & vbCrLf
    strSQL = strSQL & "  WO.DESIRED_WANT_DATE," & vbCrLf
    strSQL = strSQL & "  WO.PART_ID," & vbCrLf
    strSQL = strSQL & "  WO.WORKORDER_BASE_ID," & vbCrLf
    strSQL = strSQL & "  WO.WORKORDER_LOT_ID," & vbCrLf
    strSQL = strSQL & "  PO.OPERATION_SEQ_NO"
    snpData.Open strSQL, dbDatabase

    If snpData.State = 1 Then
        'Remove the previous run
        dbDatabase.Execute "TRUNCATE TABLE T_RESOURCE_DAILY_SCH"

        'Set up the ADO command object to use bind variables
        With comData
            strSQL = "INSERT INTO T_RESOURCE_DAILY_SCH (" & vbCrLf
            strSQL = strSQL & "  RESOURCE_ID," & vbCrLf
            strSQL = strSQL & "  START_DATE," & vbCrLf
            strSQL = strSQL & "  FINISH_DATE," & vbCrLf
            strSQL = strSQL & "  RUN_HOURS," & vbCrLf
            strSQL = strSQL & "  PART_ID," & vbCrLf
            strSQL = strSQL & "  WORKORDER_BASE_ID," & vbCrLf
            strSQL = strSQL & "  WORKORDER_LOT_ID," & vbCrLf
            strSQL = strSQL & "  OPERATION_SEQ_NO," & vbCrLf
            strSQL = strSQL & "  DESIRED_WANT_DATE," & vbCrLf
            strSQL = strSQL & "  UNIT_ASSIGNED)" & vbCrLf
            strSQL = strSQL & "VALUES (" & vbCrLf
            strSQL = strSQL & "  ?," & vbCrLf
            strSQL = strSQL & "  ?," & vbCrLf
            strSQL = strSQL & "  ?," & vbCrLf
            strSQL = strSQL & "  ?," & vbCrLf
            strSQL = strSQL & "  ?," & vbCrLf
            strSQL = strSQL & "  ?," & vbCrLf
            strSQL = strSQL & "  ?," & vbCrLf
            strSQL = strSQL & "  ?," & vbCrLf
            strSQL = strSQL & "  ?," & vbCrLf
            strSQL = strSQL & "  ?)"

            .Parameters.Append .CreateParameter("resource_id", adVarChar, adParamInput, 15, "")
            .Parameters.Append .CreateParameter("start_date", adDate, adParamInput, 8, Null)
            .Parameters.Append .CreateParameter("finish_date", adDate, adParamInput, 8, Null)
            .Parameters.Append .CreateParameter("run_hours", adNumeric, adParamInput, 12, 0)
            .Parameters.Append .CreateParameter("part_id", adVarChar, adParamInput, 30, "")
            .Parameters.Append .CreateParameter("workorder_base_id", adVarChar, adParamInput, 15, "")
            .Parameters.Append .CreateParameter("workorder_lot_id", adVarChar, adParamInput, 3, "")
            .Parameters.Append .CreateParameter("operation_seq_no", adNumeric, adParamInput, 12, 10)
            .Parameters.Append .CreateParameter("desired_want_date", adDate, adParamInput, 8, Null)
            .Parameters.Append .CreateParameter("unit_assigned", adNumeric, adParamInput, 12, 10)

            .CommandText = strSQL
            .CommandType = adCmdText
            .CommandTimeout = 30
            .ActiveConnection = dbDatabase
        End With

        dbDatabase.BeginTrans

        Do While Not snpData.EOF
            'Find the last operation date for this resource
            intResourceIndex = 0
            For i = 1 To 100
                If strResourceID(i) = snpData("resource_id") Then
                    intResourceIndex = i
                    Exit For
                End If
                If strResourceID(i) = "" Then
                    'No match found, create a new one
                    intResourceIndex = i
                    strResourceID(i) = snpData("resource_id")
                    Exit For
                End If
            Next i

            If strWorkOrderLast <> snpData("workorder_base_id") & "//" & snpData("workorder_lot_id") Then
                varWorkOrderLastDate = varScheduleStart
            End If

            If varResourceLastDate(intResourceIndex) >= varWorkOrderLastDate Then
                comData("start_date") = varResourceLastDate(intResourceIndex)
            Else
                comData("start_date") = varWorkOrderLastDate
            End If

            If varResourceLastDate(intResourceIndex) >= varWorkOrderLastDate Then
                comData("finish_date") = DateAdd("n", snpData("run_hours") * 60, varResourceLastDate(intResourceIndex))
                varWorkOrderLastDate = DateAdd("n", snpData("run_hours") * 60, varResourceLastDate(intResourceIndex))
            Else
                comData("finish_date") = DateAdd("n", snpData("run_hours") * 60, varWorkOrderLastDate)
                varWorkOrderLastDate = DateAdd("n", snpData("run_hours") * 60, varWorkOrderLastDate)
            End If

            varResourceLastDate(intResourceIndex) = varWorkOrderLastDate
            strWorkOrderLast = snpData("workorder_base_id") & "//" & snpData("workorder_lot_id")

            comData("resource_id") = snpData("resource_id")
            comData("run_hours") = snpData("run_hours")
            comData("part_id") = snpData("part_id")
            comData("workorder_base_id") = snpData("workorder_base_id")
            comData("workorder_lot_id") = snpData("workorder_lot_id")
            comData("operation_seq_no") = snpData("operation_seq_no")
            comData("desired_want_date") = snpData("desired_want_date")
            comData("unit_assigned") = 1

            'Execute the insert statement with bind variables
            comData.Execute

            snpData.MoveNext
        Loop
        snpData.Close

        'Issue a COMMIT
        dbDatabase.CommitTrans
    Else
        intResult = MsgBox("Could not query the database." & vbCrLf & Error(Err), 16, "Excel Demo")
    End If

    Set snpData = Nothing
    Set comData = Nothing
End Sub

Switch back to the Excel window and turn off Design Mode.  Clicking the Create Schedule button should populate the T_RESOURCE_DAILY_SCH table.  Once that code finishes executing, switch back to the Code Editor and add the following below the rest of the code:

Private Sub cmdDrawCalendar_Click()
    Dim i As Long
    Dim j As Integer
    Dim intFlag As Integer
    Dim intBaseRow As Integer
    Dim intBaseRowMax As Integer
    Dim intDataRow As Integer
    Dim intWeek As Integer
    Dim intResult As Integer
    Dim intWeekDay As Integer
    Dim intGraphicalMovable As Integer      'Determines if a graphical view will be created
    Dim intGraphicalFixed As Integer        'Determines if a graphical metafile will be created
    Dim strSQL As String
    Dim strLastResourceID As String
    Dim strLine As String
    Dim varWeekStart(1000) As Date
    Dim varWeekEnd(1000) As Date

    Dim lngBoxHeight As Long
    Dim lngBoxLengthMult As Single
    Dim lngFontSize As Long
    Dim lngBoxTop As Long
    Dim lngBoxLeft As Long
    Dim lngBoxWidth As Long
    Dim lngMaxCapacity As Long
    Dim varBaseDate As Date

    Dim lngResourceTop As Long     'Current top position for the resource ID

    Dim strFilename As String
    Dim snpData As ADODB.Recordset
    Dim comData As ADODB.Command

    'Don't break the code if an error is returned
    On Error Resume Next

    intGraphicalMovable = True
    intGraphicalFixed = False

    intResult = ConnectDatabase

    If intResult = False Then
        intResult = MsgBox("Could not connect to the Visual database.  Check your user name and password." & vbCrLf & Error(Err), 16, "Excel Demo")

        Exit Sub
    End If

    If intGraphicalFixed = True Then
        'See if we need to delete the worksheet from a previous execution
        For i = 1 To ActiveWorkbook.Sheets.Count
            If ActiveWorkbook.Sheets(i).Name = "ShopCalendarViewFixed" Then
                Sheets("ShopCalendarViewFixed").Select
                Application.DisplayAlerts = False
                ActiveWindow.SelectedSheets.Delete
                Application.DisplayAlerts = True
                Exit For
            End If
        Next i

        'Add a new worksheet to the workbook after the ShopCalendar worksheet
        ActiveWorkbook.Sheets.Add , Sheets("ShopCalendar")
        ActiveWorkbook.ActiveSheet.Name = "ShopCalendarViewFixed"
    End If

    If intGraphicalMovable = True Then
        'See if we need to delete the worksheet from a previous execution
        For i = 1 To ActiveWorkbook.Sheets.Count
            If ActiveWorkbook.Sheets(i).Name = "ShopCalendarView" Then
                Sheets("ShopCalendarView").Select
                Application.DisplayAlerts = False
                ActiveWindow.SelectedSheets.Delete
                Application.DisplayAlerts = True
                Exit For
            End If
        Next i

        'Add a new worksheet to the workbook after the ShopCalendar worksheet
        ActiveWorkbook.Sheets.Add , Sheets("ShopCalendar")
        ActiveWorkbook.ActiveSheet.Name = "ShopCalendarView"
    End If

    Sheets("ShopCalendar").Select

    Set snpData = New ADODB.Recordset
    Set comData = New ADODB.Command

    Sheets("ShopCalendar").Rows("13:10000").Delete Shift:=xlUp

    With comData
        strSQL = "SELECT" & vbCrLf
        strSQL = strSQL & "  RESOURCE_ID," & vbCrLf
        strSQL = strSQL & "  START_DATE," & vbCrLf
        strSQL = strSQL & "  FINISH_DATE," & vbCrLf
        strSQL = strSQL & "  RUN_HOURS," & vbCrLf
        strSQL = strSQL & "  PART_ID," & vbCrLf
        strSQL = strSQL & "  WORKORDER_BASE_ID," & vbCrLf
        strSQL = strSQL & "  WORKORDER_LOT_ID," & vbCrLf
        strSQL = strSQL & "  OPERATION_SEQ_NO," & vbCrLf
        strSQL = strSQL & "  DESIRED_WANT_DATE," & vbCrLf
        strSQL = strSQL & "  UNIT_ASSIGNED" & vbCrLf
        strSQL = strSQL & "FROM" & vbCrLf
        strSQL = strSQL & "  T_RESOURCE_DAILY_SCH" & vbCrLf
        strSQL = strSQL & "WHERE" & vbCrLf
        strSQL = strSQL & "  START_DATE<=TRUNC(SYSDATE+45)" & vbCrLf
        strSQL = strSQL & "ORDER BY" & vbCrLf
        strSQL = strSQL & "  RESOURCE_ID," & vbCrLf
        strSQL = strSQL & "  START_DATE," & vbCrLf
        strSQL = strSQL & "  FINISH_DATE," & vbCrLf
        strSQL = strSQL & "  WORKORDER_BASE_ID," & vbCrLf
        strSQL = strSQL & "  WORKORDER_LOT_ID"

        'Set up the command properties
        .CommandText = strSQL
        .CommandType = adCmdText
        .CommandTimeout = 30
        .ActiveConnection = dbDatabase
    End With

    Set snpData = comData.Execute

    varBaseDate = Date
    lngBoxLengthMult = 0.1
    lngBoxHeight = 8
    lngResourceTop = 30
    lngFontSize = 6

    varBaseDate = Date

    If Not (snpData Is Nothing) Then
    'If snpData.State = 1 Then
        Application.ScreenUpdating = False

        If intGraphicalMovable = True Then
            For i = 0 To 30
                lngBoxLeft = 70 + lngBoxLengthMult * (i * 60 * 24)
                lngBoxWidth = lngBoxLengthMult * (60 * 24) - 1
                With Sheets("ShopCalendarView").Shapes.AddTextbox(msoTextOrientationHorizontal, lngBoxLeft, lngBoxTop, lngBoxWidth, (lngBoxHeight * 2 - 1))
                    With .TextFrame
                        .Characters.Text = Format(DateAdd("d", i, varBaseDate), "m/d")
                        .Characters.Font.Size = (lngBoxHeight - 2) * 2
                    End With
                    .Fill.ForeColor.RGB = RGB(255 - 4 * (Weekday(DateAdd("d", i, varBaseDate), vbMonday) - 1), 255 - 4 * (Weekday(DateAdd("d", i, varBaseDate), vbMonday) - 1), 255 - 4 * (Weekday(DateAdd("d", i, varBaseDate), vbMonday) - 1))
                End With
            Next i
        End If

        If Not (snpData.EOF) Then
            intBaseRowMax = 10

            'Find the week start and end dates of each week
            varWeekStart(1) = CDate(Format(DateAdd("d", -Weekday(snpData("start_date"), vbMonday) + 1, snpData("start_date")), "mm/dd/yyyy"))
            varWeekEnd(1) = DateAdd("d", 6, varWeekStart(1))
            For i = 2 To 1000
                varWeekStart(i) = DateAdd("d", (i - 1) * 7, varWeekStart(1))
                varWeekEnd(i) = DateAdd("d", 6, varWeekStart(i))
            Next i

            Do While Not snpData.EOF
                If strLastResourceID <> snpData("resource_id") Then
                    'Started a new resource ID
                    If intBaseRow > 10 Then
                        'Write out the week start dates for the last resource, and draw the borders
                        For i = intBaseRow To intBaseRowMax - 1
                            Sheets("ShopCalendar").Cells(i + 1, 1).Value = Format(varWeekStart(i - intBaseRow + 1), "mm/dd/yyyy")
                        Next i

                        Sheets("ShopCalendar").Range("B" & Format(intBaseRow) & ":H" & Format(intBaseRowMax)).Select
                        With Selection.Borders(xlEdgeLeft)
                            .Weight = xlThick
                        End With
                        With Selection.Borders(xlEdgeTop)
                            .Weight = xlThick
                        End With
                        With Selection.Borders(xlEdgeBottom)
                            .Weight = xlThick
                        End With
                        With Selection.Borders(xlEdgeRight)
                            .Weight = xlThick
                        End With
                        With Selection.Borders(xlInsideVertical)
                            .Weight = xlThin
                        End With
                        With Selection.Borders(xlInsideHorizontal)
                            .Weight = xlThin
                        End With
                    End If

                    If intGraphicalMovable = True Then
                        'For the graphical view
                        lngResourceTop = lngResourceTop + lngMaxCapacity * (lngBoxHeight) + 10
                        lngBoxLeft = 1
                        lngBoxWidth = 40
                        lngBoxTop = lngResourceTop

                        With Sheets("ShopCalendarView").Shapes.AddTextbox(msoTextOrientationHorizontal, lngBoxLeft, lngBoxTop, lngBoxWidth, (lngBoxHeight - 1))
                            With .TextFrame
                                '.AutoMargins = False
                                .Characters.Text = snpData("resource_id")
                                .Characters.Font.Size = lngBoxHeight - 2
                                .MarginLeft = 0
                                .MarginRight = 0
                                .MarginTop = 0
                                .MarginBottom = 0
                            End With
                            .Fill.ForeColor.RGB = RGB(255, 255, 255)
                        End With
                    End If

                    lngMaxCapacity = 0

                    intBaseRow = intBaseRowMax + 4
                    strLastResourceID = snpData("resource_id")

                    Sheets("ShopCalendar").Cells(intBaseRow - 1, 1).Value = snpData("resource_id")
                    Sheets("ShopCalendar").Cells(intBaseRow - 1, 1).Font.Bold = True
                    Sheets("ShopCalendar").Cells(intBaseRow - 1, 1).Font.Size = 14
                    For i = 1 To 7
                        Sheets("ShopCalendar").Cells(intBaseRow, i + 1).Value = Format(DateAdd("d", i - 1, varWeekStart(1)), "DDD")
                        Sheets("ShopCalendar").Cells(intBaseRow, i + 1).Font.Bold = True
                    Next i
                End If

                If Not IsNull(snpData("unit_assigned")) Then
                   If lngMaxCapacity < snpData("unit_assigned") Then
                        'Need to update the maximum unit assigned value
                        lngMaxCapacity = snpData("unit_assigned")
                    End If

                    If (varBaseDate < snpData("start_date")) Or (varBaseDate < snpData("finish_date")) Then
                        If lngMaxCapacity < snpData("unit_assigned") Then
                            lngMaxCapacity = snpData("unit_assigned")
                        End If

                        If intGraphicalMovable = True Then
                            lngBoxLeft = 70 + lngBoxLengthMult * (DateDiff("n", varBaseDate, snpData("start_date")))
                            lngBoxWidth = lngBoxLengthMult * ((DateDiff("n", varBaseDate, snpData("finish_date")) - DateDiff("n", varBaseDate, snpData("start_date"))))
                            lngBoxTop = lngResourceTop + snpData("unit_assigned") * lngBoxHeight

                            With Sheets("ShopCalendarView").Shapes.AddTextbox(msoTextOrientationHorizontal, lngBoxLeft, lngBoxTop, lngBoxWidth, (lngBoxHeight - 1))
                                With .TextFrame
                                    '.AutoMargins = False
                                    .Characters.Text = snpData("workorder_base_id") & "/" & snpData("workorder_lot_id") & " OP " & CStr(snpData("operation_seq_no"))
                                    .Characters.Font.Size = lngBoxHeight - 2
                                    .MarginLeft = 0
                                    .MarginRight = 0
                                    .MarginTop = 0
                                    .MarginBottom = 0
                                End With
                                If snpData("finish_date") > snpData("desired_want_date") Then
                                    .Fill.ForeColor.RGB = RGB(255, 0, 0)
                                Else
                                    .Fill.ForeColor.RGB = RGB(0, 255, 255)
                                End If
'                                If snpData("workorder_sub_id") = "0" Then
                                    .AlternativeText = "WO:" & snpData("workorder_base_id") & "/" & snpData("workorder_lot_id") & " OP:" & CStr(snpData("operation_seq_no")) & Chr(10) & "Part ID:" & snpData("part_id") & Chr(10) & Format(snpData("start_date"), "mm/dd/yyyy hh:nn") & " - " & Format(snpData("finish_date"), "mm/dd/yyyy hh:nn") & Chr(10) & snpData("resource_id")
'                                Else
'                                    .AlternativeText = "WO:" & snpData("workorder_base_id") & "-" & snpData("workorder_sub_id") & "/" & snpData("workorder_lot_id") & " OP:" & CStr(snpData("sequence_no")) & Chr(10) & "Part ID:" & snpData("part_id") & Chr(10) & Format(snpData("start_date"), "mm/dd hh:nn") & " - " & Format(snpData("finish_date"), "mm/dd hh:nn") & Chr(10) & snpData("resource_id")
'                                End If
                            End With
                        End If
                    End If
                End If
                For i = 0 To DateDiff("d", CDate(Format(snpData("start_date"), "mm/dd/yyyy")), CDate(Format(snpData("finish_date"), "mm/dd/yyyy")))
                    intWeekDay = Weekday(DateAdd("d", i, snpData("start_date")), vbMonday) 'Column
                    'Find the row to place the information into
                    For j = 1 To 1000
                        If varWeekEnd(j) >= CDate(Format(DateAdd("d", i, snpData("start_date")), "mm/dd/yyyy")) Then
                            'Found the week for this record
                            intDataRow = intBaseRow + j

                            If intDataRow > intBaseRowMax Then
                                intBaseRowMax = intDataRow
                            End If
                            Exit For
                        End If
                    Next j
                    If (i = 0) And (i = DateDiff("d", CDate(Format(snpData("start_date"), "mm/dd/yyyy")), CDate(Format(snpData("finish_date"), "mm/dd/yyyy")))) Then
                        'Need to include the start time and the end time
                        strLine = Format(snpData("start_date"), "hh:nn") & " - " & Format(snpData("finish_date"), "hh:nn") & "  " & snpData("part_id")
                    Else
                        If i = 0 Then
                            'Need to include the start time
                            strLine = Format(snpData("start_date"), "hh:nn") & " - C0:00" & "  " & snpData("part_id")
                        Else
                            If i = DateDiff("d", CDate(Format(snpData("start_date"), "mm/dd/yyyy")), CDate(Format(snpData("finish_date"), "mm/dd/yyyy"))) Then
                                'Need to include the end time
                                strLine = "C0:00" & " - " & Format(snpData("finish_date"), "hh:nn") & "  " & snpData("part_id")
                            Else
                                'Operation is continuing through this date
                                strLine = "C0:00" & " - C0:00" & "  " & snpData("part_id")
                            End If
                        End If
                    End If
                    strLine = strLine & "  " & snpData("workorder_base_id")
'                    If snpData("workorder_sub_id") <> "0" Then
'                        strLine = strLine & "-" & snpData("workorder_sub_id")
'                    End If
                    strLine = strLine & "/" & snpData("workorder_lot_id")
                    strLine = strLine & " OP " & Format(snpData("operation_seq_no"))

                    If Sheets("ShopCalendar").Cells(intDataRow, intWeekDay + 1).Value = "" Then
                        'Writing into a new cell
                        Sheets("ShopCalendar").Cells(intDataRow, intWeekDay + 1).Value = strLine
                    Else
                        'Writing into a cell already containing data
                        Sheets("ShopCalendar").Cells(intDataRow, intWeekDay + 1).Value = Sheets("ShopCalendar").Cells(intDataRow, intWeekDay + 1).Value & Chr(10) & strLine
                    End If
                Next i
                snpData.MoveNext
            Loop

            If intBaseRow > 10 Then
                'Write out the week start dates for the last resource
                For i = intBaseRow To intBaseRowMax - 1
                    Sheets("ShopCalendar").Cells(i + 1, 1).Value = Format(varWeekStart(i - intBaseRow + 1), "mm/dd/yyyy")
                Next i

                Sheets("ShopCalendar").Range("B" & Format(intBaseRow) & ":H" & Format(intBaseRowMax)).Select
                Selection.Borders(xlDiagonalDown).LineStyle = xlNone
                Selection.Borders(xlDiagonalUp).LineStyle = xlNone
                With Selection.Borders(xlEdgeLeft)
                    .Weight = xlThick
                End With
                With Selection.Borders(xlEdgeTop)
                    .Weight = xlThick
                End With
                With Selection.Borders(xlEdgeBottom)
                    .Weight = xlThick
                End With
                With Selection.Borders(xlEdgeRight)
                    .Weight = xlThick
                End With
                With Selection.Borders(xlInsideVertical)
                    .Weight = xlThin
                End With
                With Selection.Borders(xlInsideHorizontal)
                    .Weight = xlThin
                End With
            End If
        End If
        snpData.Close
    End If

    Sheets("ShopCalendar").Rows("10:" & Format(intBaseRowMax)).VerticalAlignment = xlTop

    Sheets("ShopCalendar").PageSetup.PrintArea = "$A$13:$H$" & Format(intBaseRowMax)
    With Sheets("ShopCalendar").PageSetup
        .CenterFooter = "Generated " & Format(Now, "mm/dd/yyyy hh:nn")
        .RightFooter = "Page &P of &N"
        .LeftMargin = Application.InchesToPoints(0.25)
        .RightMargin = Application.InchesToPoints(0.25)
        .TopMargin = Application.InchesToPoints(0.75)
        .BottomMargin = Application.InchesToPoints(0.75)
        .HeaderMargin = Application.InchesToPoints(0.3)
        .FooterMargin = Application.InchesToPoints(0.3)
        .PrintGridlines = False
        .Orientation = xlLandscape
         '.PaperSize = xlPaperLetter
        .Zoom = False
        .FitToPagesWide = 1
        .FitToPagesTall = 300
    End With

    Sheets("ShopCalendar").Range("B2").Select
    ActiveWindow.FreezePanes = True

    Sheets("ShopCalendar").Columns("B:H").Select
    Selection.ColumnWidth = 44
    Selection.WrapText = True

    Sheets("ShopCalendar").Columns("A:A").ColumnWidth = 10.43
    Sheets("ShopCalendar").Range("A13").Select
    Application.ScreenUpdating = True

    Set snpData = Nothing
    Set comData = Nothing
End Sub

With the above code added, the Draw Calendar button should now work.  Click the Draw Calendar button, the output should look something like what you see below:

A second tab is added to the worksheet that shows a graphical overview, with cyan colored bars representing operations that will complete before the work order due date and red colored bars representing operations that will complete after the work order due date:

I may post a follow-up at some point that shows how to generate an enhanced Windows metafile (EMF) which shows the graphical view of the resource utilization.