Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Andrew.

Asked: December 24, 2005 - 12:02 pm UTC

Last updated: December 26, 2005 - 10:28 am UTC

Version: 9.2.0

Viewed 10K+ times! This question is

You Asked

Hi Tom,

I was reading your "Expert Oracle Database Architecture" where it says, that (p. 409) "The second technique that works with ON COMMIT PRESERVE ROWS global temporary tables is to user GATHER_TABLE_STATS directly on that table. You would populate the global temporary table ... and then execute GATHER_TABLE_STATS on that global temporary table".

This sounds quite simple, but in my opinion there is a caveat in such a technique.
Consider:

Sess #1> select * from v$version where rownum = 1;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.6.0 - Production

Sess #1> create global temporary table gtt (x int)
2 on commit PRESERVE rows;

Table created.

Sess #1> insert into gtt
2 select rownum
3 from all_objects
4 where rownum <= 1000;

1000 rows created.

Sess #1> commit;

Commit complete.

Sess #1> select count(*) from gtt;

COUNT(*)
----------
1000

Now, let’s start another session and issue the same query:

Sess #2> select count(*) from gtt;

COUNT(*)
----------
0

So far so good -- no rows, which is an obvious result. Now, let’s return back to our first session, gather statistics on temporary table and look closer:

Sess #1> exec dbms_stats.GATHER_TABLE_STATS (ownname => user, tabname => 'GTT')

PL/SQL procedure successfully completed.

Sess #1> set autotrace traceonly exp
Sess #1>
Sess #1> select * from gtt;

1000 rows selected.

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1000 Bytes=3000)
1 0 TABLE ACCESS (FULL) OF 'GTT' (Cost=4 Card=1000 Bytes=3000)

And this is good, too. We gathered statistics, so CBO can make better decisions now. It’s easy to notice that cardinality estimate was 100% accurate here.

But what about our second session?

Sess #2> set autotrace on exp
Sess #2>
Sess #2> select * from gtt;

no rows selected

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1000 Bytes=3000)
1 0 TABLE ACCESS (FULL) OF 'GTT' (Cost=4 Card=1000 Bytes=3000)

Here, things become worse. Our query returned no rows but AUTOTRACE shows us that estimated cardinality is 1000, just if we were running this in a first session.

So, if you suggest the following technique:
1. Insert data into temporary table
2. Gather statistics on this table.
3. Run queries against temporary table
-- then I’m having problems with it, because an example above shows that, in concurrent environment this approach would be negatively affected.

Suppose, we have two concurrent sessions and they both use the same temporary table. What will happen if the following events do occur in the order specified?

12:00 am. Session 1 loaded 1 000 000 rows into temp table.
12:01 am. Session 2 loaded 100 rows into the same temp table.
12.02 am. Session 2 finished gathering statistics on this temp table.
12.03 am. Session 1 finished gathering statistics.
12.04 am. Session 2 issued some query against temp table and CBO faced wrong statistics, because it was just re-gathered by session 1.
12.05 am. Session 1 issued its own query...

So, as long as I see, there’s possible caveat in this technique. I would personally prefer using DYNAMIC_SAMPLING, or maybe CARDINALITY hint to make CBO happy with temporary tables.

And what do you think of this?

Thanks in advance and best regards,
Andrew.

and Tom said...

The goal was to populate the global temporary table with REPRESENTATIVE statistics - statistics that are going to be "in general representative of what you expect in this table"

<quote>
This problem is particularly noticeable with temporary tables. There is simply no way to analyze them and gather statistics. You could use DBMS_STATS.SET_TABLE_STATS to put in representative statistics, but that would be problematic if you didn’t know what the representative statistics should be, or if they changed from run to run. (In Oracle8i and earlier, this was your only option.) Also, the defaults for temporary tables are different from the defaults for regular tables.
</quote>


If there is no such "representative set of statistics", then you are in a "rock and hard place" sort of situation. You could use dynamic sampling:

<quote>
Interestingly, the temporary table appears to be much larger and the real table smaller. Enter dynamic sampling. Using this feature, the optimizer will, at query optimization time, dynamically gather just enough statistics on the referenced object to come up with a more meaningful, correct plan—in the most likely event that your temporary tables do not typically have 8,168 rows (if they do, you won’t need this).

For the OPTIMIZER_DYNAMIC_SAMPLING parameter, the valid values are 0 through 10, with 0 meaning don’t do any sample and 10 being the most aggressive. Level 1 is the default in Oracle9i Release 2 if the OPTIMIZER_FEATURES_ENABLED parameter is set to 9.2 or above. Otherwise, level 0 is the default. Table 6-5 describes each setting for this parameter.
</quote>

but even so - that only happens during a hard parse - so if YOU hard parse with a global temporary filled with a 1,000 rows - and when I run the same query I only have 1 - it'll use the plan generated for 1,000 rows.

Query Plans with Temporary Tables
</code> https://www.oracle.com/technetwork/issue-archive/2014/14-sep/o54asktom-2279184.html <code>
Discusses this in more detail.

Rating

  (5 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Thank you Tom

Andrew Max, December 24, 2005 - 2:03 pm UTC

Tom,

Thanks a lot for your reply, this helped much.
However, it seems that I am missing your point here:

>> "... but even so - that only happens during a hard parse - so if YOU hard parse with a global temporary filled with a 1,000 rows - and when I run the same query I only have 1 - it'll use the plan generated for 1,000 rows."

Ok, then why I am getting the following results? Here I issued the *same query* with DYNAMIC_SAMPLING hint three times, while adding more and more rows into temporary table before each run.

SQL> create global temporary table gtt (x int)
  2  on commit preserve rows;

Table created.

SQL> insert into gtt
  2  select rownum from dual connect by level <= 10;

10 rows created.

SQL> commit;

Commit complete.

SQL> set autotrace traceonly exp
SQL> select /*+ DYNAMIC_SAMPLING(4) */ * from gtt;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=45 Card=10 Bytes=130)
   1    0   TABLE ACCESS (FULL) OF 'GTT' (Cost=45 Card=10 Bytes=130)



SQL> set autotrace off
SQL>
SQL> insert into gtt
  2  select rownum from dual connect by level <= 10;

10 rows created.

SQL> commit;

Commit complete.

SQL> set autotrace traceonly exp
SQL> select /*+ DYNAMIC_SAMPLING(4) */ * from gtt;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=45 Card=20 Bytes=260)
   1    0   TABLE ACCESS (FULL) OF 'GTT' (Cost=45 Card=20 Bytes=260)



SQL> set autotrace off
SQL>
SQL> insert into gtt
  2  select rownum from dual connect by level <= 10;

10 rows created.

SQL> commit;

Commit complete.

SQL> set autotrace traceonly exp
SQL> select /*+ DYNAMIC_SAMPLING(4) */ * from gtt;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=45 Card=30 Bytes=390)
   1    0   TABLE ACCESS (FULL) OF 'GTT' (Cost=45 Card=30 Bytes=390)

It’s easy to see that cardinality estimates are different, they accurately reflect what we have in our table.

I used single session in this test, but it's easy to verify that DYNAMIC_SAMPLING hint also works well if temp table is modified concurrently by multiple sessions.

Moreover, "Oracle9i Database Performance Tuning Guide and Reference" tells us, that:
"Dynamic sampling is repeatable if no rows have been inserted, deleted, or updated in the table being sampled."

Could you please comment more on this?...

Best regards,
Andrew. 

Tom Kyte
December 24, 2005 - 2:34 pm UTC

because the explain plan is always doing a hard parse. (there are a couple of times explain plan can be "misleading")

Consider:


ops$tkyte@ORA10GR2> create global temporary table gtt ( x int ) on commit preserve rows;
Table created.

ops$tkyte@ORA10GR2> create index t_idx on gtt(x);
Index created.

ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> insert into gtt select rownum from dual connect by level <= 10;
10 rows created.

ops$tkyte@ORA10GR2> commit;
Commit complete.

ops$tkyte@ORA10GR2> alter session set sql_trace=true;
Session altered.

ops$tkyte@ORA10GR2> select /*+ dynamic_sampling(4) */ * from gtt XX where x = 1;

         X
----------
         1

ops$tkyte@ORA10GR2> alter session set sql_trace=false;
Session altered.


<b>Right now, explain plan says "thou shalt do a full scan"</b>

ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> set autotrace traceonly explain
ops$tkyte@ORA10GR2> select /*+ dynamic_sampling(4) */ * from gtt where x = 1;

Execution Plan
----------------------------------------------------------
Plan hash value: 917624683

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    13 |     2   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| GTT  |     1 |    13 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------

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

   1 - filter("X"=1)

Note
-----
   - dynamic sampling used for this statement

ops$tkyte@ORA10GR2> set autotrace off
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> insert into gtt select rownum+10 from dual connect by level <= 10;

10 rows created.

ops$tkyte@ORA10GR2> commit;

Commit complete.

ops$tkyte@ORA10GR2> set autotrace traceonly explain
ops$tkyte@ORA10GR2> select /*+ dynamic_sampling(4) */ * from gtt where x = 1;

Execution Plan
----------------------------------------------------------
Plan hash value: 917624683

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    13 |     2   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| GTT  |     1 |    13 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------

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

   1 - filter("X"=1)

Note
-----
   - dynamic sampling used for this statement

ops$tkyte@ORA10GR2> set autotrace off
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> insert into gtt select rownum+20 from dual connect by level <= 10;

10 rows created.

ops$tkyte@ORA10GR2> commit;

Commit complete.

ops$tkyte@ORA10GR2> set autotrace traceonly explain
ops$tkyte@ORA10GR2> select /*+ dynamic_sampling(4) */ * from gtt where x = 1;

Execution Plan
----------------------------------------------------------
Plan hash value: 2946670127

--------------------------------------------------------------------------
| Id  | Operation        | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT |       |     1 |    13 |     1   (0)| 00:00:01 |
|*  1 |  INDEX RANGE SCAN| T_IDX |     1 |    13 |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------

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

   1 - access("X"=1)

Note
-----
   - dynamic sampling used for this statement

<b>by the time we get to 30 rows however - it says "you would do a index range scan IF YOU WERE TO HARD PARSE RIGHT NOW

So, let's run that one again:</b>

ops$tkyte@ORA10GR2> set autotrace off
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> alter session set sql_trace=true;

Session altered.

ops$tkyte@ORA10GR2> select /*+ dynamic_sampling(4) */ * from gtt XX where x = 1;

         X
----------
         1

ops$tkyte@ORA10GR2> select /*+ dynamic_sampling(4) */ * from gtt YY where x = 1;

         X
----------
         1

ops$tkyte@ORA10GR2> alter session set sql_trace=false;

Session altered.


<b>tkprof says (aggregate=no) </b>

select /*+ dynamic_sampling(4) */ * from gtt XX where x = 1

...

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 63

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL GTT (cr=4 pr=0 pw=0 time=131 us)
********************************************************************************
select /*+ dynamic_sampling(4) */ * from gtt XX where x = 1

...

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 63

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL GTT (cr=4 pr=0 pw=0 time=91 us)


<b>since we soft parsed the second time, we just reused the plan...</b>

(but at least now I have a third example to add to my all about binds talk where I show how "autotrace" can "lie" sometimes...)

select /*+ dynamic_sampling(4) */ * from gtt YY where x = 1

...

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 63

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  INDEX RANGE SCAN T_IDX (cr=2 pr=0 pw=0 time=54 us)(object id 53711)

<b>but when we hard parsed the "same" query (changed XX to YY) - it did do the dynamic sample (hard parse) and did pick a different plan</b>


 

Ok, got it

Andrew Max, December 24, 2005 - 3:04 pm UTC

>> "because the explain plan is always doing a hard parse..."

Doh! For sure, this is what I missed out, my apologies.

And now, I remember that I was already caught in the same EXPLAIN PLAN trap someday before.
Seems I haven’t learnt that lesson well ... ;))

Thank you Tom!

Kind regards,
Andrew.

Tom Kyte
December 24, 2005 - 3:11 pm UTC

No problem - it is an "obscure" thing actually - good to have the question asked and subsequently answered (so others "see" it as well :)

A reader, December 25, 2005 - 11:59 pm UTC

Tom,

I tried the above example on my 10G r2 linux desktop and this is what i get.

test@AMER.ORACLE.COM> create global temporary table gtt ( x int ) on commit preserve rows;

Table created.

test@AMER.ORACLE.COM> create index t_idx on gtt (x);

Index created.

test@AMER.ORACLE.COM> insert into gtt select rownum from dual connect by level <=10;

10 rows created.

test@AMER.ORACLE.COM> commit;

Commit complete.

test@AMER.ORACLE.COM> alter session set sql_trace=true;

Session altered.

test@AMER.ORACLE.COM> selecct /*+ dynamic_sampling(4) */ * from gtt XX where x=1;
SP2-0734: unknown command beginning "selecct /*..." - rest of line ignored.
test@AMER.ORACLE.COM> select /*+ dynamic_sampling(4) */ * from gtt XX where x=1;

X
----------
1

test@AMER.ORACLE.COM> alter session set sql_trace=false;

Session altered.

test@AMER.ORACLE.COM> set autotrace traceonly explain
test@AMER.ORACLE.COM> select /*+ dynamic_sampling(4) */ * from gtt where x=1;

Execution Plan
----------------------------------------------------------
Plan hash value: 2946670127

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 3 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| T_IDX | 1 | 13 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------

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

1 - access("X"=1)

Note
-----
- dynamic sampling used for this statement

test@AMER.ORACLE.COM> set autotrace off
test@AMER.ORACLE.COM> insert into gtt select rownum +10 from dual connect by level <=10;

10 rows created.

test@AMER.ORACLE.COM> commit;

Commit complete.

test@AMER.ORACLE.COM> set autotrace traceonly explain
test@AMER.ORACLE.COM> select /*+ dynamic_sampling(4) */ * from gtt where x=1;

Execution Plan
----------------------------------------------------------
Plan hash value: 2946670127

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 2 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| T_IDX | 1 | 13 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------

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

1 - access("X"=1)

Note
-----
- dynamic sampling used for this statement

test@AMER.ORACLE.COM> insert into gtt select rownum +20 from dual connect by level <=10;

10 rows created.


Execution Plan
----------------------------------------------------------
Plan hash value: 1731520519

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | 4 (0)| 00:00:01 |
| 1 | COUNT | | | | |
| 2 | CONNECT BY WITHOUT FILTERING| | | | |
| 3 | FAST DUAL | | 1 | 4 (0)| 00:00:01 |
------------------------------------------------------------------------------

test@AMER.ORACLE.COM> commit;

Commit complete.

test@AMER.ORACLE.COM> select /*+ dynamic_sampling(4) */ * from gtt where x=1;

Execution Plan
----------------------------------------------------------
Plan hash value: 2946670127

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| T_IDX | 1 | 13 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------

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

1 - access("X"=1)

Note
-----
- dynamic sampling used for this statement

test@AMER.ORACLE.COM>

Tom Kyte
December 26, 2005 - 10:28 am UTC

connect /
drop table gtt;

connect /
set linesize 1000
set echo on

create global temporary table gtt ( x int ) on commit preserve rows;
create index t_idx on gtt (x);
insert into gtt select rownum from dual connect by level <=10;
commit;
alter session set sql_trace=true;
select /*+ dynamic_sampling(4) */ * from gtt XX where x=1;
alter session set sql_trace=false;
set autotrace traceonly explain
select /*+ dynamic_sampling(4) */ * from gtt where x=1;
set autotrace off
insert into gtt select rownum +10 from dual connect by level <=10;
commit;
set autotrace traceonly explain
select /*+ dynamic_sampling(4) */ * from gtt where x=1;
set autotrace off
insert into gtt select rownum +20 from dual connect by level <=10;
commit;
set autotrace traceonly explain
select /*+ dynamic_sampling(4) */ * from gtt where x=1;
set autotrace off
alter session set sql_trace=true;
select /*+ dynamic_sampling(4) */ * from gtt XX where x=1;
alter session set sql_trace=false;

was my script

ora10gr2.__db_cache_size=482344960
ora10gr2.__java_pool_size=8388608
ora10gr2.__large_pool_size=4194304
ora10gr2.__shared_pool_size=104857600
ora10gr2.__streams_pool_size=0
*.background_dump_dest='/home/ora10gr2/admin/ora10gr2/bdump'
*.compatible='10.2.0.1.0'
*.control_files='/home/ora10gr2/oradata/ora10gr2/control01.ctl','/home/ora10gr2/oradata/ora10gr2/control02.ctl','/home/ora10gr2/oradata/ora10gr2/control03.ctl'
*.core_dump_dest='/home/ora10gr2/admin/ora10gr2/cdump'
*.db_block_size=8192
*.db_create_file_dest='/home/ora10gr2/oradata/ora10gr2'
*.db_domain=''
*.db_name='ora10gr2'
*.dispatchers='(PROTOCOL=TCP) (SERVICE=ora10gr2XDB)'
*.job_queue_processes=10
*.open_cursors=300
*.os_authent_prefix='OPS$'
*.pga_aggregate_target=201326592
*.processes=150
*.remote_login_passwordfile='EXCLUSIVE'
*.sga_target=605028352
*.undo_management='AUTO'
*.undo_tablespace='UNDOTBS1'
*.user_dump_dest='/home/ora10gr2/admin/ora10gr2/udump'

was my init.  something is different in your environment is all. 

A reader, December 26, 2005 - 12:00 am UTC

And this is the output from my tkprof.

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

select /*+ dynamic_sampling(4) */ *
from
gtt XX where x=1


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 1 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.00 0.00 0 2 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.00 0.00 0 3 0 1

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 41

Rows Row Source Operation
------- ---------------------------------------------------
1 INDEX RANGE SCAN T_IDX (cr=2 pr=0 pw=0 time=40 us)(object id 15456)

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


So what?

Andrew Max, December 26, 2005 - 7:40 am UTC

Hi "a reader",

In fact, temporary table had only one column in it, which is indexed. So, given the number of rows is very small, the query "select * from gtt where x = 1" can result either in full scan or in index range scan without subsequent access to the table. It all depends ...

However, the fact Tom demonstrated above is still a fact: dynamic sampling is done only if query is being hard parsed, and EXPLAIN PLAN can mislead us in this case. By the way, it’s easy to "prove again" even without resorting to TKPROF:

SQL> select * from v$version where rownum = 1;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.1.0.4.0 - Prod

SQL> column OPERATION format a50
SQL>
SQL> create global temporary table gtt (x int, y char(100))
  2  on commit preserve rows;

Table created.

SQL> create index idx_gtt on gtt (x);

Index created.

SQL> insert into gtt select rownum, '*' from dual
  2  connect by level <= 10;

10 rows created.

SQL> select /*+ DYNAMIC_SAMPLING(4) */ * from gtt where x = 1;

         X Y
---------- --------------------------------------------------------------------
         1 *

Now, I will insert a bunch of rows into our temporary table and issue the same query again:

SQL> insert into gtt select rownum + 10, '*' from dual
  2  connect by level <= 1000;

1000 rows created.

SQL> commit;

Commit complete.

SQL> select /*+ DYNAMIC_SAMPLING(4) */ * from gtt where x = 1;

         X Y
---------- --------------------------------------------------------------------
         1 *

Ok, let’s look in V$SQL and V$SQL_PLAN:

SQL> select s.ADDRESS, s.HASH_VALUE
  2    from v$sql s
  3   where s.SQL_TEXT like 'select /*+ DYNAMIC_SAMPLING(4) */ * from gtt%';

ADDRESS  HASH_VALUE
-------- ----------
21F42B4C 3107799210

SQL> select p.ID, p.PARENT_ID pid, lpad(' ', p.DEPTH, ' ') || p.OPERATION ||
  2    decode(p.OPTIONS, null, null, ' (' || p.OPTIONS || ')') OPERATION
  3    from v$sql_plan p
  4   where p.ADDRESS = '21F42B4C'
  5     and p.HASH_VALUE = 3107799210;

        ID        PID OPERATION
---------- ---------- --------------------------------------------------
         0            SELECT STATEMENT
         1          0  TABLE ACCESS (FULL)

It’s easy to see that we still have only one shared SQL area, and that corresponding plan implies full scan of our temp table.

Now, let’s issue slightly modified query with alias for temp table:

SQL> select /*+ DYNAMIC_SAMPLING(4) */ * from gtt TT where x = 1;

         X Y
---------- --------------------------------------------------------------------
         1 *

SQL> select s.ADDRESS, s.HASH_VALUE
  2    from v$sql s
  3   where s.SQL_TEXT like 'select /*+ DYNAMIC_SAMPLING(4) */ * from gtt TT%';

ADDRESS  HASH_VALUE
-------- ----------
222B8F70 4244129192

SQL> select p.ID, p.PARENT_ID pid, lpad(' ', p.DEPTH, ' ') || p.OPERATION ||
  2    decode(p.OPTIONS, null, null, ' (' || p.OPTIONS || ')') OPERATION
  3    from v$sql_plan p
  4   where p.ADDRESS = '222B8F70'
  5     and p.HASH_VALUE = 4244129192;

        ID        PID OPERATION
---------- ---------- --------------------------------------------------
         0            SELECT STATEMENT
         1          0  TABLE ACCESS (BY INDEX ROWID)
         2          1   INDEX (RANGE SCAN)

Things have changed as you can see ...

Cheers,
Andrew. 

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library