Skip to Main Content
  • Questions
  • Joining date and timestamp partitioned tables but partition pruning seems to not to work

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, lh.

Asked: November 28, 2018 - 7:01 am UTC

Last updated: November 30, 2018 - 3:31 pm UTC

Version: 12.1

Viewed 1000+ times

You Asked

Hi

We have date and timestamp partitioned tables. While joining them partition pruning seems to not to work.

Is there any way to do this?

create table T_A(
ORDERED  DATE       not null,
C1       number(20) not null
)
partition by range(ORDERED)
interval (NUMTOYMINTERVAL(1,'MONTH'))
(
    PARTITION DEFAULT_PARTITION VALUES LESS THAN (TO_DATE('20180101','yyyymmdd'))
);

create table T_B(
INSERTED TIMESTAMP  not null,
C1       number(20) not null
)
partition by range(INSERTED)
interval (NUMTOYMINTERVAL(1,'MONTH'))
(
    PARTITION DEFAULT_PARTITION VALUES LESS THAN (TO_DATE('20180101','yyyymmdd'))
);

insert into t_a(ordered , c1) select to_date('20180101','YYYYMMDD') + level , level from dual connect by level < 500;
insert into t_B(inserted, c1) select to_date('20180101','YYYYMMDD') + level + 3/2, level from dual connect by level < 500;
commit;
execute dbms_stats.gather_table_stats(user,tabname => 'T_A');
execute dbms_stats.gather_table_stats(user,tabname => 'T_B');
select 
    t_a.ORDERED,
    t_b.INSERTED,
    t_a.c1    
from
    t_a
    inner join t_b on T_A.C1 = t_b.c1 and T_A.ORDERED <= t_b.INSERTED
where 
    T_A.ORDERED >= to_date('20180701','yyyymmdd')
;
select * from table(dbms_xplan.display_cursor);
--------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |      |       |       |  1176 (100)|          |       |       |
|*  1 |  HASH JOIN                |      |    16 |   432 |  1176   (1)| 00:00:01 |       |       |
|   2 |   PARTITION RANGE ITERATOR|      |   320 |  3840 |   462   (1)| 00:00:01 |     8 |1048575|
|*  3 |    TABLE ACCESS FULL      | T_A  |   320 |  3840 |   462   (1)| 00:00:01 |     8 |1048575|
|   4 |   PARTITION RANGE ALL     |      |   499 |  7485 |   713   (1)| 00:00:01 |     1 |1048575|
|   5 |    TABLE ACCESS FULL      | T_B  |   499 |  7485 |   713   (1)| 00:00:01 |     1 |1048575|
--------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("T_A"."C1"="T_B"."C1")
       filter("T_B"."INSERTED">=INTERNAL_FUNCTION("T_A"."ORDERED"))
   3 - filter("T_A"."ORDERED">=TO_DATE(' 2018-07-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
 

select 
    t_a.ORDERED,
    t_b.INSERTED,
    t_a.c1    
from
    t_a
    inner join t_b on T_A.C1 = t_b.c1 and T_A.ORDERED <= t_b.INSERTED
where 
    T_A.ORDERED >= to_date('20180701','yyyymmdd')
and T_A.ORDERED = cast (T_A.ORDERED as timestamp)
and cast(T_A.ORDERED as timestamp) <= T_B.INSERTED
;


| Id  | Operation                 | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |      |       |       |  1176 (100)|          |       |       |
|*  1 |  HASH JOIN                |      |     1 |    27 |  1176   (1)| 00:00:01 |       |       |
|   2 |   PARTITION RANGE ITERATOR|      |     3 |    36 |   462   (1)| 00:00:01 |     8 |1048575|
|*  3 |    TABLE ACCESS FULL      | T_A  |     3 |    36 |   462   (1)| 00:00:01 |     8 |1048575|
|   4 |   PARTITION RANGE ALL     |      |   499 |  7485 |   713   (1)| 00:00:01 |     1 |1048575|
|   5 |    TABLE ACCESS FULL      | T_B  |   499 |  7485 |   713   (1)| 00:00:01 |     1 |1048575|
--------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("T_A"."C1"="T_B"."C1")
       filter(("T_B"."INSERTED">=INTERNAL_FUNCTION("T_A"."ORDERED") AND 
              "T_B"."INSERTED">=CAST(INTERNAL_FUNCTION("T_A"."ORDERED") AS timestamp)))
   3 - filter(("T_A"."ORDERED">=TO_DATE(' 2018-07-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') 
              AND INTERNAL_FUNCTION("T_A"."ORDERED")=CAST(INTERNAL_FUNCTION("T_A"."ORDERED") AS 
              timestamp)))






and Chris said...

You can cast the timestamp to a date. Which enables the optimizer to apply the filter to T_B. But not partition prune:

select 
    t_a.ORDERED,
    t_b.INSERTED,
    t_a.c1    
from t_a
join t_b 
on T_A.C1 = t_b.c1 
and T_A.ORDERED <= cast ( t_b.INSERTED as date )
where T_A.ORDERED >= to_date('20180701','yyyymmdd');

select * 
from   table(dbms_xplan.display_cursor(null, null, 'ROWSTATS +PARTITION LAST')); 

Plan hash value: 4264623528                                                             
                                                                                        
-------------------------------------------------------------------------------------   
| Id  | Operation                 | Name | Starts | E-Rows | Pstart| Pstop | A-Rows |   
-------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT          |      |      1 |        |       |       |   1276 |   
|*  1 |  HASH JOIN                |      |      1 |     64 |       |       |   1276 |   
|   2 |   PARTITION RANGE ITERATOR|      |      1 |    639 |     8 |1048575|    638 |   
|*  3 |    TABLE ACCESS FULL      | T_A  |     11 |    639 |     8 |1048575|    638 |   
|   4 |   PARTITION RANGE ALL     |      |      1 |    998 |     1 |1048575|    640 |   
|*  5 |    TABLE ACCESS FULL      | T_B  |     18 |    998 |     1 |1048575|    640 |   
-------------------------------------------------------------------------------------   
                                                                                        
Predicate Information (identified by operation id):                                     
---------------------------------------------------                                     
                                                                                        
   1 - access("T_A"."C1"="T_B"."C1")                                                    
       filter("T_A"."ORDERED"<=CAST(INTERNAL_FUNCTION("T_B"."INSERTED") AS              
              date ))                                                                   
   3 - filter("T_A"."ORDERED">=TIMESTAMP' 2018-07-01 00:00:00')                         
   5 - filter(CAST(INTERNAL_FUNCTION("T_B"."INSERTED") AS date )>=TIMESTAMP'            
              2018-07-01 00:00:00')      


The problem is the database needs to do a date <> timestamp conversion. Whether you do this explicitly or let it happen implicitly, you'll have a function on at least one of the partitioning columns.

Applying functions to the partitioning column(s) limits the optimizer's ability to partition prune.

To be sure of doing this, you need to change the data types to match:

drop table t_a cascade constraints purge;
drop table t_b cascade constraints purge;

create table T_A(
ORDERED  TIMESTAMP  not null,
C1       number(20) not null
)
partition by range(ORDERED)
interval (NUMTOYMINTERVAL(1,'MONTH'))
(
    PARTITION DEFAULT_PARTITION VALUES LESS THAN (TO_DATE('20180101','yyyymmdd'))
);

create table T_B(
INSERTED TIMESTAMP  not null,
C1       number(20) not null
)
partition by range(INSERTED)
interval (NUMTOYMINTERVAL(1,'MONTH'))
(
    PARTITION DEFAULT_PARTITION VALUES LESS THAN (TO_DATE('20180101','yyyymmdd'))
);

insert into t_a(ordered , c1) select to_date('20180101','YYYYMMDD') + level , level from dual connect by level < 500;
insert into t_B(inserted, c1) select to_date('20180101','YYYYMMDD') + level + 3/2, level from dual connect by level < 500;
commit;
execute dbms_stats.gather_table_stats(user,tabname => 'T_A');
execute dbms_stats.gather_table_stats(user,tabname => 'T_B');

select 
    t_a.ORDERED,
    t_b.INSERTED,
    t_a.c1    
from t_a
join t_b 
on T_A.C1 = t_b.c1 
and T_A.ORDERED <= t_b.INSERTED 
where T_A.ORDERED >= to_timestamp('20180701','yyyymmdd');

select * 
from   table(dbms_xplan.display_cursor(null, null, 'ROWSTATS +PARTITION LAST')); 

drop table t_a cascade constraints purge;
drop table t_b cascade constraints purge;

create table T_A(
ORDERED  TIMESTAMP  not null,
C1       number(20) not null
)
partition by range(ORDERED)
interval (NUMTOYMINTERVAL(1,'MONTH'))
(
    PARTITION DEFAULT_PARTITION VALUES LESS THAN (TO_DATE('20180101','yyyymmdd'))
);

create table T_B(
INSERTED TIMESTAMP  not null,
C1       number(20) not null
)
partition by range(INSERTED)
interval (NUMTOYMINTERVAL(1,'MONTH'))
(
    PARTITION DEFAULT_PARTITION VALUES LESS THAN (TO_DATE('20180101','yyyymmdd'))
);

insert into t_a(ordered , c1) select to_date('20180101','YYYYMMDD') + level , level from dual connect by level < 500;
insert into t_B(inserted, c1) select to_date('20180101','YYYYMMDD') + level + 3/2, level from dual connect by level < 500;
commit;
execute dbms_stats.gather_table_stats(user,tabname => 'T_A');
execute dbms_stats.gather_table_stats(user,tabname => 'T_B');

select 
    t_a.ORDERED,
    t_b.INSERTED,
    t_a.c1    
from t_a
join t_b 
on T_A.C1 = t_b.c1 
and T_A.ORDERED <= t_b.INSERTED 
where T_A.ORDERED >= to_timestamp('20180701','yyyymmdd');

select * 
from   table(dbms_xplan.display_cursor(null, null, 'ROWSTATS +PARTITION LAST')); 

Plan hash value: 4124582613                                                             
                                                                                        
-------------------------------------------------------------------------------------   
| Id  | Operation                 | Name | Starts | E-Rows | Pstart| Pstop | A-Rows |   
-------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT          |      |      1 |        |       |       |    319 |   
|*  1 |  HASH JOIN                |      |      1 |    161 |       |       |    319 |   
|   2 |   PARTITION RANGE ITERATOR|      |      1 |    320 |     8 |1048575|    319 |   
|*  3 |    TABLE ACCESS FULL      | T_A  |     11 |    320 |     8 |1048575|    319 |   
|   4 |   PARTITION RANGE ITERATOR|      |      1 |    321 |     8 |1048575|    320 |   
|*  5 |    TABLE ACCESS FULL      | T_B  |     11 |    321 |     8 |1048575|    320 |   
-------------------------------------------------------------------------------------   
                                                                                        
Predicate Information (identified by operation id):                                     
---------------------------------------------------                                     
                                                                                        
   1 - access("T_A"."C1"="T_B"."C1")                                                    
       filter("T_A"."ORDERED"<="T_B"."INSERTED")                                        
   3 - filter("T_A"."ORDERED">=TIMESTAMP' 2018-07-01 00:00:00.000000000')               
   5 - filter("T_B"."INSERTED">=TIMESTAMP' 2018-07-01 00:00:00.000000000') 

Rating

  (6 ratings)

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

Comments

Workarround

Gh, November 28, 2018 - 12:21 pm UTC

Add not null virtual column to T_B as a truck or conversion of INSERTED to a simple Date.
Then add constraint relying both columns
Then optimizer should use some pruning.


Chris Saxon
November 28, 2018 - 2:00 pm UTC

I'm not sure what you mean by "add constraint relying both columns".

In any case, I couldn't get that option to help with partition pruning:

create table T_A(
ORDERED  DATE       not null,
C1       number(20) not null
)
partition by range(ORDERED)
interval (NUMTOYMINTERVAL(1,'MONTH'))
(
    PARTITION DEFAULT_PARTITION VALUES LESS THAN (TO_DATE('20180101','yyyymmdd'))
);

create table T_B(
INSERTED TIMESTAMP  not null,
C1       number(20) not null,
vc       date as ( cast ( inserted as date ) ) not null 
)
partition by range(INSERTED)
interval (NUMTOYMINTERVAL(1,'MONTH'))
(
    PARTITION DEFAULT_PARTITION VALUES LESS THAN (TO_DATE('20180101','yyyymmdd'))
);

insert into t_a(ordered , c1) select to_date('20180101','YYYYMMDD') + level , level from dual connect by level < 500;
insert into t_B(inserted, c1) select to_date('20180101','YYYYMMDD') + level + 3/2, level from dual connect by level < 500;
commit;
execute dbms_stats.gather_table_stats(user,tabname => 'T_A');
execute dbms_stats.gather_table_stats(user,tabname => 'T_B');
select 
    t_a.ORDERED,
    t_b.INSERTED,
    t_a.c1    
from t_a
join t_b 
on T_A.C1 = t_b.c1 
and T_A.ORDERED <= t_b.INSERTED
where T_A.ORDERED >= to_date('20180701','yyyymmdd');
    
select * 
from   table(dbms_xplan.display_cursor(null, null, 'ROWSTATS +PARTITION LAST'));

Plan hash value: 4264623528                                                             
                                                                                        
-------------------------------------------------------------------------------------   
| Id  | Operation                 | Name | Starts | E-Rows | Pstart| Pstop | A-Rows |   
-------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT          |      |      1 |        |       |       |    319 |   
|*  1 |  HASH JOIN                |      |      1 |     16 |       |       |    319 |   
|   2 |   PARTITION RANGE ITERATOR|      |      1 |    320 |     8 |1048575|    319 |   
|*  3 |    TABLE ACCESS FULL      | T_A  |     11 |    320 |     8 |1048575|    319 |   
|   4 |   PARTITION RANGE ALL     |      |      1 |    499 |     1 |1048575|    499 |   
|   5 |    TABLE ACCESS FULL      | T_B  |     18 |    499 |     1 |1048575|    499 |   
-------------------------------------------------------------------------------------   
                                                                                        
Predicate Information (identified by operation id):                                     
---------------------------------------------------                                     
                                                                                        
   1 - access("T_A"."C1"="T_B"."C1")                                                    
       filter("T_B"."INSERTED">=INTERNAL_FUNCTION("T_A"."ORDERED"))                     
   3 - filter("T_A"."ORDERED">=TO_DATE(' 2018-07-01 00:00:00', 'syyyy-mm-dd             
              hh24:mi:ss'))

Could query be transformed to something like this ?

lh, November 28, 2018 - 2:14 pm UTC

Hi

Thank You for Your answer.

I was hoping that by adding
and T_A.ORDERED = cast (T_A.ORDERED as timestamp)
and cast(T_A.ORDERED as timestamp) <= T_B.INSERTED

The actual executed code would have been transformed into something like :
select
t_a.ORDERED,
t_b.INSERTED,
t_a.c1
from
t_a
inner join t_b on T_A.C1 = t_b.c1 and T_A.ORDERED <= t_b.INSERTED
where
T_A.ORDERED >= to_date('20180701','yyyymmdd')
and T_A.ORDERED = cast (T_A.ORDERED as timestamp)
and cast(T_A.ORDERED as timestamp) <= T_B.INSERTED
and T_B.INSERTED >= to_date('20180701','yyyymmdd');


--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 925 (100)| | | |
|* 1 | HASH JOIN | | 1 | 27 | 925 (1)| 00:00:01 | | |
| 2 | PARTITION RANGE ITERATOR| | 3 | 36 | 462 (1)| 00:00:01 | 8 |1048575|
|* 3 | TABLE ACCESS FULL | T_A | 3 | 36 | 462 (1)| 00:00:01 | 8 |1048575|
| 4 | PARTITION RANGE ITERATOR| | 321 | 4815 | 462 (1)| 00:00:01 | 8 |1048575|
|* 5 | TABLE ACCESS FULL | T_B | 321 | 4815 | 462 (1)| 00:00:01 | 8 |1048575|
--------------------------------------------------------------------------------------------------

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

1 - access("T_A"."C1"="T_B"."C1")
filter(("T_B"."INSERTED">=INTERNAL_FUNCTION("T_A"."ORDERED") AND
"T_B"."INSERTED">=CAST(INTERNAL_FUNCTION("T_A"."ORDERED") AS timestamp)))
3 - filter(("T_A"."ORDERED">=TO_DATE(' 2018-07-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')
AND INTERNAL_FUNCTION("T_A"."ORDERED")=CAST(INTERNAL_FUNCTION("T_A"."ORDERED") AS
timestamp)))
5 - filter("T_B"."INSERTED">=TIMESTAMP' 2018-07-01 00:00:00')


The idea being, that statement is actually a view and we don't know what is the date given in view's where clause.


lh, November 28, 2018 - 2:17 pm UTC

Hi

New attempt with more readable format.

Thank You for Your answer.

I was hoping that by adding
and T_A.ORDERED = cast (T_A.ORDERED as timestamp)
and cast(T_A.ORDERED as timestamp) <= T_B.INSERTED

The actual executed code would have been transformed into something like :
select 
 t_a.ORDERED,
 t_b.INSERTED,
 t_a.c1 
from
 t_a
 inner join t_b on T_A.C1 = t_b.c1 and T_A.ORDERED <= t_b.INSERTED
where 
 T_A.ORDERED >= to_date('20180701','yyyymmdd')
and T_A.ORDERED = cast (T_A.ORDERED as timestamp)
and cast(T_A.ORDERED as timestamp) <= T_B.INSERTED
and T_B.INSERTED >= to_date('20180701','yyyymmdd');


--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 925 (100)| | | |
|* 1 | HASH JOIN | | 1 | 27 | 925 (1)| 00:00:01 | | |
| 2 | PARTITION RANGE ITERATOR| | 3 | 36 | 462 (1)| 00:00:01 | 8 |1048575|
|* 3 | TABLE ACCESS FULL | T_A | 3 | 36 | 462 (1)| 00:00:01 | 8 |1048575|
| 4 | PARTITION RANGE ITERATOR| | 321 | 4815 | 462 (1)| 00:00:01 | 8 |1048575|
|* 5 | TABLE ACCESS FULL | T_B | 321 | 4815 | 462 (1)| 00:00:01 | 8 |1048575|
--------------------------------------------------------------------------------------------------

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

 1 - access("T_A"."C1"="T_B"."C1")
 filter(("T_B"."INSERTED">=INTERNAL_FUNCTION("T_A"."ORDERED") AND 
 "T_B"."INSERTED">=CAST(INTERNAL_FUNCTION("T_A"."ORDERED") AS timestamp)))
 3 - filter(("T_A"."ORDERED">=TO_DATE(' 2018-07-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') 
 AND INTERNAL_FUNCTION("T_A"."ORDERED")=CAST(INTERNAL_FUNCTION("T_A"."ORDERED") AS 
 timestamp)))
 5 - filter("T_B"."INSERTED">=TIMESTAMP' 2018-07-01 00:00:00')


The idea being, that statement is actually a view and we don't know what is the date given in view's where clause.


Chris Saxon
November 28, 2018 - 4:39 pm UTC

Well, if you're happy to apply the filter to columns in both tables, then yes, you'll get partition pruning! :)

You can ditch all the casting too:

select 
 t_a.ORDERED,
 t_b.INSERTED,
 t_a.c1 
from  t_a
join  t_b 
on    T_A.C1 = t_b.c1 
and   T_A.ORDERED <= t_b.INSERTED
where T_A.ORDERED >= to_date('20180701','yyyymmdd')
and   T_B.INSERTED >= to_date('20180701','yyyymmdd');

select * 
from   table(dbms_xplan.display_cursor(null, null, 'ROWSTATS +PARTITION LAST'));
Plan hash value: 4124582613                                                             
                                                                                        
-------------------------------------------------------------------------------------   
| Id  | Operation                 | Name | Starts | E-Rows | Pstart| Pstop | A-Rows |   
-------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT          |      |      1 |        |       |       |    319 |   
|*  1 |  HASH JOIN                |      |      1 |     16 |       |       |    319 |   
|   2 |   PARTITION RANGE ITERATOR|      |      1 |    320 |     8 |1048575|    319 |   
|*  3 |    TABLE ACCESS FULL      | T_A  |     11 |    320 |     8 |1048575|    319 |   
|   4 |   PARTITION RANGE ITERATOR|      |      1 |    321 |     8 |1048575|    320 |   
|*  5 |    TABLE ACCESS FULL      | T_B  |     11 |    321 |     8 |1048575|    320 |   
-------------------------------------------------------------------------------------   
                                                                                        
Predicate Information (identified by operation id):                                     
---------------------------------------------------                                     
                                                                                        
   1 - access("T_A"."C1"="T_B"."C1")                                                    
       filter("T_B"."INSERTED">=INTERNAL_FUNCTION("T_A"."ORDERED"))                     
   3 - filter("T_A"."ORDERED">=TO_DATE(' 2018-07-01 00:00:00', 'syyyy-mm-dd             
              hh24:mi:ss'))                                                             
   5 - filter("T_B"."INSERTED">=TIMESTAMP' 2018-07-01 00:00:00')  

Well ..

Gh, November 28, 2018 - 6:13 pm UTC

Something like this..

.....
vc date as ( cast ( inserted as date ) ) not null 
Check vc <= inserted) ......

select t_a.ORDERED, t_b.INSERTED, t_a.c1 from t_a join t_b on T_A.C1 = t_b.c1 and T_A.ORDERED <= t_b.vc where T_A.ORDERED >= to_date('20180701','yyyymmdd');

Date vs timestamp. Query transformation.

lh, November 30, 2018 - 8:39 am UTC

Hi

If I have

A_date > to_date('20180101','yyyymmdd')
and B_ts > A_date

and then I tell that A_date = A_ts and B_ts > A_ts
so doesn't it imply:

A > to_date('20180101','yyyymmdd')
and B_ts > A_date
and B_ts > A_ts
and B_ts > to_date('20180101','yyyymmdd')


What I was hoping that Oracle would transform query to that effect. This could be done despite they are of different datatypes.


In general timestamps and dates are both used in systems and it would be benecial that use of indexes and partitioning are not affected by type mismatches.


lh
Chris Saxon
November 30, 2018 - 3:31 pm UTC

Timestamps and dates are different data types. So the database has to convert one to the other. This means applying a function to one of the columns.

Applying a function to a column => you can't use regular indexes & partition pruning won't happen.

If you want to avoid type mismatches, create columns with the same data type!

Query transformation/date\timestamp difference

lh, December 03, 2018 - 7:32 am UTC

Hi

I understand this type conversion problem, but the transformation of query I hopefully were wishing for does not require conversions of columns at the runtime.
Only conversion would be on B_ts > to_date('20180101','yyyymmdd') and there it wouldn't matter, if to_date('20180101','yyyymmdd') would be converted to timestamp.
Optimizer has previously done some amazing query transformations so I was little bit disappointed this time.


In general, I sure would like that timestamps and date columns would operate better together. I had previusly erraneously thought that timestamps are just extensions of dates and they could be used together.
Timestamps are very usefull for fields which require more precision than one seconds. So data warehouse systems would contain both of them.


lh

More to Explore

Administration

Need more information on Administration? Check out the Administrators guide for the Oracle Database