High-speed Data Loading and Rolling Window Operations With Partitioning
High-Speed Data Loading and Rolling Window Operations
With Partitioning
In this tutorial, you learn how to use Oracle10g
for high-speed data loading and leveraging Oracle Partitioning for a rolling
window operation.
Approximately 2 hours
This tutorial covers the following topics:
Place
the cursor over this icon to load and view all the screenshots for this tutorial.
(Caution: Because this action loads all screenshots simultaneously, response
time may be slow depending on your Internet connection.)
Note: Alternatively, you can place the cursor
over each individual icon in the following steps to load and view only the screenshot
associated with that step.
Most of the time, OLTP (Source) systems, which feed the Data Warehouse, are not directly connected to the data warehousing system for extracting new data. Commonly, those OLTP systems send data feeds in the form of external files. This data must be loaded into the Data Warehouse, preferably in parallel, thus leveraging the existing resources.
Furthermore, due to the business needs and disk space constraints of the sample company MyCompany, only the data of the last three years are relevant for the analysis needs. This means that with the insertion of new data, disk
space has to be freed, either by purging the old data or by leveraging Oracle's table compression. The maintenance of this so-called rolling window operation is done with Oracle Partitioning.
Back to Topic List
External Tables
To load the external files into their data warehouse, MyCompany uses the Oracle10g external table feature, which allows external data, such as flat files, to be exposed within the database just like a regular database table. External tables can be accessed by SQL, so that external files can be queried directly and in parallel using the full power of SQL, PL/SQL, and Java. External tables are often used in the Extraction, Transformation, and Loading (ETL) process to combine data-transformations (through SQL) with data-loading into a single step. External tables are a very powerful feature with many possible applications in ETL and other database environments where flat files are processed; it is an alternative to using SQL*Loader.
Parallel Execution
Parallel execution dramatically reduces response time for data-intensive operations on large databases typically associated with decision support systems (DSS) and data warehouses. You can also implement parallel execution on certain types of online transaction processing (OLTP) and hybrid systems. Simply expressed, parallelism is the idea of breaking down a task so that, instead of one process doing all of the work in a query, many processes do parts of the work at the same time. An example of this is when four processes handle four different quarters in a year instead of one process handling all four quarters by itself.
Rolling Window Operations Leveraging Oracle Partitioning
A very important task in the back office of a data warehouse is to keep the data synchronized with the various changes that are taking place in the OLTP (source) systems. In addition, the life span of the data from an analysis perspective is very often limited, so that older data must be purged from the target system while new data is loaded; this operation is often called a rolling window operation. This should be done as fast as possible without any implication for the concurrent online access of the data warehousing system.
Back to Topic List
Before starting this tutorial, you should have:
Back to Topic List
Some changes on the existing Sales History schema objects
are necessary, and some additional system privileges must be granted to the
user SH. The SQL file for applying those changes is modifySH_10g.sql. This
file is provided to you in the setup_dwh.zip file.
To utilize the setup files for the Data Warehousing tutorials, perform the following
steps:
| 1. |
Open a terminal window and execute the following commands:
cd /home/oracle/wkdir
sqlplus sh/sh
@modifySH_10g

|
Back to Topic List
In this section of the tutorial, you will load data into the Data Warehouse
using External Tables, introduced in Oracle9i Release 1.
To show you how external tables can be created and used, perform the following steps:
Back to Topic List
1. To Create the Necessary Directory Objects, Perform the Following Steps:
Before you can create the external table, you will need to create a directory object in the database that will point to the directory on the file system where the data files will reside. Optionally, you can separate the location for the log, bad and discard files from the location of the data files. To create the directory, perform the following:
| 1. |
From a SQL*Plus session logged on to the SH schema, run create_directory.sql, or copy the following SQL statement into your SQL*Plus session:
@create_directory.sql
DROP DIRECTORY data_dir;
DROP DIRECTORY log_dir;
CREATE DIRECTORY data_dir AS '/home/oracle/wkdir'; CREATE DIRECTORY log_dir AS '/tmp'; 
The scripts are set up for a Linux system and assume that the Hands-On workshop was
extracted on drive /home/oracle/wkdir. Please
note that due to security reasons, symbolic links are not supported as DIRECTORY objects within the database.
|
Back to List
2. Create the Table:
When creating an external table, you are defining two parts of information:
| 1. |
The metadata information for the table representation inside the database |
| 2. |
The HOW access parameter definition to extract the data from the external file |
After the creation of this meta information, the external data can be accessed from within the database, without the necessity of an initial load.
To create the external table, you perform the following steps:
| 1. |
From a SQL*Plus session logged on to the SH schema, run @create_external_table.sql. The results are as follows:
@create_external_table
REM 10gR1 set echo on Rem ***** Rem CREATE EXTERNAL TABLE Rem ***** DROP TABLE sales_delta_XT;
CREATE TABLE sales_delta_XT
(
PROD_ID NUMBER,
CUST_ID NUMBER,
TIME_ID DATE,
CHANNEL_ID CHAR(2),
PROMO_ID NUMBER,
QUANTITY_SOLD NUMBER(3),
AMOUNT_SOLD NUMBER(10,2)
)
ORGANIZATION external
(
TYPE oracle_loader
DEFAULT DIRECTORY data_dir
ACCESS PARAMETERS
(
RECORDS DELIMITED BY NEWLINE CHARACTERSET US7ASCII
BADFILE log_dir:'sh_sales.bad'
LOGFILE log_dir:'sh_sales.log_xt'
FIELDS TERMINATED BY "|" LDRTRIM
(prod_id, cust_id,
time_id CHAR(11) DATE_FORMAT DATE MASK "DD-MON-YYYY",
channel_id, promo_id, quantity_sold, amount_sold
)
)
location
(
'salesDec01.dat'
)
)REJECT LIMIT UNLIMITED NOPARALLEL

You can view information about external tables through the following data dictionary views:
- [USER | ALL| DBA]_EXTERNAL_TABLES
- [ALL| DBA]_DIRECTORIES
- [USER | ALL| DBA]_EXTERNAL_LOCATIONS
|
Back to List
3. Select From the External Table:
You can now access the data in the external file without any further action, as shown with the following SQL command:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following queries (Note that you can run the select_et.sql script file.):
@select_et.sql
SELECT COUNT(*) FROM sales_delta_xt;
SELECT MAX(time_id) FROM sales_delta_xt;
If you copied the files correctly, the maximum TIME_ID is 31-DEC-01. 
|
Back to List
4. Transparent High-Speed Parallel
Access of External Tables:
Unlike SQL*Loader, the access of external tables can be done in parallel independent of the number of external files. SQL*Loader can only operate on a per-file bases; so that you have to split large source files manually if you want to parallelize. With external tables, the degree of parallelism is controlled exactly the same way than for a normal table. In this case, you defined the external table NOPARALLEL by default. The following section will show you how to control the degree of parallelism on statement level by using a hint.
| 1. |
The script parallel_select_from_ET.sql contains the SQL statements for the next three steps.
Run the following query, from a SQL*Plus session logged on to the SH schema, to see the current parallel session statistics:
@parallel_select_from_ET.sql
SELECT *
FROM v$pq_sesstat
WHERE statistic in ('Queries Parallelized',
'Allocation Height');

|
| 2. |
Run the same query you used before to access the external table with a parallel degree of 4, controlled with a hint. The select statement is:
@parallel_select_from_ET_2.sql
SELECT /*+ parallel(a,4) */ COUNT(*)
FROM sales_delta_XT a;
You are selecting from the external table in parallel, although the external table points only to one input source file. file. Alternatively, you could change the PARALLEL property of the external table with an ALTER TABLE command:
rem ALTER TABLE sales_delta_XT PARALLEL 4;

|
| 3. |
Now rerun the session stats again to see
the differences. You will see the parallel session statistics have
changed; it shows that the last query was parallelized, and it also
show the degree of parallelism.
@parallel_select_from_ET.sql
SELECT *
FROM v$pq_sesstat
WHERE statistic in
('Queries Parallelized', 'Allocation Height');

|
Back to List
5. Parallel Insert for High Speed Loading:
Oracle10g provides unlimited parallel direct path INSERT capabilities within each partition. The execution plan can be used to determine whether or not the INSERT will be done in parallel; alternatively you can check the execution plan of an operation after it was running right out of the SQL cache without the necessity of an explain plan command at all.
Lets first look how a serial plan looks like. Since none of the objects is defined in parallel, you will get serial execution unless you (a) either change the default degree of parallelism of one of the objects or (b) use a hint.
| 1. |
To show the execution plan for SERIAL INSERT behavior, run show_serial_exec_plan.sql, or copy the following SQL statement into your SQL*Plus session:
@show_serial_exec_plan.sql
Rem 10gR1
EXPLAIN PLAN FOR INSERT /*+ APPEND */ INTO sales
(
PROD_ID,
CUST_ID,
TIME_ID,
CHANNEL_ID,
PROMO_ID,
QUANTITY_SOLD,
AMOUNT_SOLD
)
SELECT
PROD_ID,
CUST_ID,
TIME_ID,
case CHANNEL_ID
when 'S' then 3
when 'T' then 9
when 'C' then 5
when 'I' then 4
when 'P' then 2
else 99
end,
PROMO_ID,
sum(QUANTITY_SOLD),
sum(AMOUNT_SOLD)
FROM SALES_DELTA_XT
GROUP BY 1, prod_id,time_id,cust_id,channel_id,promo_id;
set linesize 140
set pagesize 40
SELECT * FROM TABLE(dbms_xplan.display);
|
| 2. |
To show the PARALLEL INSERT execution plan you will need to run show_parallel_exec_plan.sql
logged in to the SH schema. A parallel DML must always be the first statement
of a transaction. Furthermore, a DML operation cannot run in the presence
of primary key foreign key constraints. Therefore, you have to disable
the constraints prior to the parallel DML operation:
@show_parallel_exec_plan.sql
Rem 10gR1 Rem ***** Rem SHOW PARALLEL EXECUTION PLAN Rem ***** Rem ***** PROMPT need to set parallel DML explicitly PROMPT and to disable constraints !!! Rem ***** ALTER TABLE sales DISABLE CONSTRAINT sales_product_fk; ALTER TABLE sales DISABLE CONSTRAINT sales_customer_fk; ALTER TABLE sales DISABLE CONSTRAINT sales_time_fk; ALTER TABLE sales DISABLE CONSTRAINT sales_channel_fk; ALTER TABLE sales DISABLE CONSTRAINT sales_promo_fk;
COMMIT;
ALTER SESSION ENABLE PARALLEL DML;
Rem *****
PROMPT SHOW PARALLEL EXECUTION PLAN
Rem *****
EXPLAIN PLAN FOR INSERT /*+ APPEND PARALLEL(SALES,4) */ INTO sales
(
PROD_ID,
CUST_ID,
TIME_ID,
CHANNEL_ID,
PROMO_ID,
QUANTITY_SOLD,
AMOUNT_SOLD
)
SELECT /*+ parallel (sales_delta_XT,4) */
PROD_ID,
CUST_ID,
TIME_ID,
case CHANNEL_ID
when 'S' then 3
when 'T' then 9
when 'C' then 5
when 'I' then 4
when 'P' then 2
else 99
end,
PROMO_ID,
sum(QUANTITY_SOLD),
sum(AMOUNT_SOLD)
FROM SALES_DELTA_XT
GROUP BY 1, prod_id,time_id,cust_id,channel_id,promo_id;
set linesize 140
set pagesize 40
SELECT * FROM TABLE(dbms_xplan.display);

|
Back to List
6. Perform a Parallel Insert:
In this step of the tutorial you will do the parallel insert discussed before. Note that you not only select the data from the external table but also do an aggregate as part of the select prior to the insertion. You are combining a
transformation with the actual loading process—something that is impossible with SQL*Loader only.
| 1. |
You will now run the SQL to perform a parallel insert. Run parallel_insert_file.sql or copy the following statement into your SQL*Plus session set timing on.
@parallel_insert_file.sql
Rem 10gR1 set timing on
COMMIT; ALTER SESSION ENABLE PARALLEL DML;
Rem ***** Rem PARALLEL INSERT Rem ***** INSERT /*+ APPEND PARALLEL(SALES,4) */ INTO sales ( PROD_ID, CUST_ID, TIME_ID, CHANNEL_ID, PROMO_ID, QUANTITY_SOLD, AMOUNT_SOLD ) SELECT /*+ PARALLEL (sales_delta_XT,4) */ PROD_ID, CUST_ID, TIME_ID, case CHANNEL_ID when 'S' then 3 when 'T' then 9 when 'C' then 5 when 'I' then 4 when 'P' then 2 else 99 end, PROMO_ID, sum(QUANTITY_SOLD), sum(AMOUNT_SOLD) FROM SALES_DELTA_XT GROUP BY 1, prod_id,time_id,cust_id,channel_id,promo_id;
set timing off
SELECT * FROM TABLE(dbms_xplan.display_cursor);
Rem Alternatively, you can control the successful parallel DML with a V$ view SELECT *
FROM v$pq_sesstat
WHERE statistic in ('DML Parallelized','Allocation Height');
Write down the execution time of this statement and compare it to the total amount of time you will need with SQL*Loader and a subsequent insertion. Note that you will not see the full benefit of parallelizing the external table access and combining the transformation with the loading, because you are accessing a very small amount of data in parallel on a single CPU machine using one disk.


|
| 2. |
Row rollback. In the next tutorial the same data is inserted using SQL*Loader.
ROLLBACK;
|
| 5. |
After issuing a rollback, you need to enable the constraints again.
@enable_cons.sql
ALTER TABLE sales
MODIFY CONSTRAINT sales_product_fk ENABLE NOVALIDATE;
ALTER TABLE sales
MODIFY CONSTRAINT sales_customer_fk ENABLE NOVALIDATE;
ALTER TABLE sales
MODIFY CONSTRAINT sales_time_fk ENABLE NOVALIDATE;
ALTER TABLE sales
MODIFY CONSTRAINT sales_channel_fk ENABLE NOVALIDATE;
ALTER TABLE sales
MODIFY CONSTRAINT sales_promo_fk ENABLE NOVALIDATE;

You have just loaded and transformed the data as one step. To accomplish
the same functionality with SQL*Loader, you will need TWO process steps,
which means more work effort and slower performance for the complete process.
|
Back to List
Back to Topic List
Before Oracle9i, you did the same operation by using SQL*Loader. The external table method you previously performed is the preferred method of data loading and transformation. However, to demonstrate the benefit of using external tables, you will perform the tasks to load and transform the data by using SQL*Loader.
To show how to load and transform data by using SQL*Loader, you will perform the following steps:
1. Create a Staging Table:
You need a staging table to load the data into so that you can transform it within the database in a second step.
| 1. |
You will now run the sql script create_stage.sql to create a staging table:
@create_stage.sql
CREATE TABLE sales_dec01 AS SELECT * FROM sales WHERE 1=0;
ALTER TABLE sales_dec01 MODIFY (channel_id CHAR(2) null);
|
Back to List
2. Load Data into the Staging Table
by Using SQL*Loader
The scripts are set up for a Linux system and assume that
the Hands-On workshop was extracted on /tmp. You perform this operation from
the OS command line, in the /home/oracle/wkdir
directory.
You will now load the data file using sales_dec01.ctl into the staging table by performing the following steps:
| 1. |
Open a new terminal window. Execute the following commands:
cd /home/oracle/wkdir
sqlldr sh/sh control=sales_dec01.ctl direct=true

Note: You may need to specify your database alias when connecting using
SQL*Loader. For example, if your database alias is
o10g1, start SQL*Loader with the statement:
sqlldr sh/sh@o10g1 control=sales_dec01.ctl direct=true
|
| 2. |
Note that you cannot parallelize this task. Check the SQL*Loader log file sales_dec01.log and write down the execution time for the loading process.

Unlike with an External Table, space is consumed in the database for making the data accessible from within the database. The space consumed by the staging table is linearly dependent on the amount of data to be loaded for further transformation.
Also note that it is not reasonably possible to parallelize the loading with SQL*Loader without having several external files. You can use the SKIP option for several Loader processes, accessing the same file. However, this enforces every SQL*Loader process to scan the whole external file; this is detrimental to the overall performance.
The information about space usage of an object can be accessed through the following data dictionary views:
[USER | ALL| DBA]_SEGMENTS
[USER | ALL| DBA]_EXTENTS |
Back to List
3. Load the Staging Table into the Target Database:
After loading the external data—making it accessible for the database—you can perform your transformation.
| 1. |
You will now run SQL to transform and insert the external data, which is already staged in the database, into the sales fact table by issuing @load_stage_table.sql.
@load_stage_table.sql
REM 10gR1 Rem Load from staging table into target set timing on INSERT /*+ APPEND */ INTO sales ( PROD_ID, CUST_ID, TIME_ID, CHANNEL_ID, PROMO_ID, QUANTITY_SOLD, AMOUNT_SOLD ) SELECT PROD_ID, CUST_ID, TIME_ID, case CHANNEL_ID when 'S' then 3 when 'T' then 9 when 'C' then 5 when 'I' then 4 when 'P' then 2 else 99 end, PROMO_ID, sum(QUANTITY_SOLD), sum(AMOUNT_SOLD) FROM sales_dec01 GROUP BY prod_id,time_id,cust_id,channel_id,promo_id;
set timing off

|
Back to List
4. Drop the Staging Table:
You can now drop or truncate the staging table to free its consumed space.
| 1. |
You can now drop the staging table information:
@drop_sales_dec01.sql>
DROP TABLE sales_dec01;
|
Processing this simple loading and transformation process with external tables enabled you to combine the loading with the transformation, thus simplifying and speeding up the process.
Furthermore, an initial staging of the data in the database is not necessary with external tables, thus saving space consumption. The larger the external data volume, the more you will save staging space and processing time with external tables compared to SQL*Loader.
Back to List
Back to Topic List
After successfully loading the December data into the Q4 partition
of your SALES fact table, this partition will encounter none to minimal further
DML operations. This makes the table partition an optimal candidate for being
stored using Oracle's table compression functionality that was introduced in
Oracle9i Release 2. Data stored in relational databases keeps growing
as businesses require more information. A big portion of
the cost of keeping large amounts of data is in the cost of disk systems, and
the resources utilized in managing that data. Oracle9i Release 2 Enterprise
Edition introduced a unique way to deal with this cost by compressing data stored
in relational tables with virtually no negative impact on query time against
that data, thereby enabling substantial cost savings.
Commercially available relational database systems have not heavily utilized compression techniques on data stored in relational tables. One reason is that the trade-off between time and space for compression is not always attractive for relational databases. A typical compression technique may offer space savings, but only at a cost of much increased query time against the data. Furthermore, many of the standard techniques do not even guarantee
that data size does not increase after compression.
Oracle Database 10g Enterprise Edition offers a unique compression
technique that is very attractive for large data warehouses. It is unique in
many ways. Its reduction of disk space can be significantly higher than standard
compression algorithms, because it is optimized for relational data. It has
virtually no negative impact on the performance of queries against compressed
data; in fact, it may have a significant positive impact on queries accessing
large amounts of data, as well as on data management operations like backup
and recovery. It ensures that compressed data is never larger than uncompressed
data.
1. Compress the Most Recent Partition:
To measure the tremendous benefits of table compression, let's first ensure that the most recent partition does not have compression enabled and how large it is. You can do this by running the part_before_compression.sql script or by copying the following SQL:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following statements:
@part_before_compression.sql
PROMPT Space consumption before compression
COLUMN partition_name FORMAT a50 COLUMN segment_name FORMAT a50
SELECT partition_name, compression FROM user_tab_partitions
WHERE partition_name='SALES_Q4_2001';
SELECT segment_name, bytes/(1024*1024) MB FROM user_segments
WHERE segment_name='SALES' AND partition_name='SALES_Q4_2001';
|
| 2. |
Now compress the partition and transparently maintain all existing indexes.
All local and global indexes will be maintained as part of this SQL statement.
The functionality of online index maintenance for Partition Maintenance
Operation is discussed later in this tutorial.
Note that compressing a partition is not an in-place compression, but
that you create a new compressed segment and remove the old uncompressed
one at the end of the operation.
From a SQL*Plus session logged on to the SH schema, execute the following statements:
@compress_salesQ4_2001.sql
PROMPT now compress the partition including index maintenance (details later)) ALTER TABLE sales MOVE PARTITION sales_q4_2001 COMPRESS UPDATE INDEXES;
|
| 3. |
Let's now see how much space the new compressed partition is allocating and compare it to the size of the uncompressed partition:
@part_after_compression.sql
PROMPT Space consumption AFTER compression
SELECT partition_name, compression FROM user_tab_partitions WHERE partition_name='SALES_Q4_2001';
SELECT segment_name, bytes/(1024*1024) MB FROM user_segments WHERE segment_name='SALES' AND partition_name='SALES_Q4_2001';

Most likely the compression ratio with real-world data will be higher
than the one experienced with the sales history schema. Compression ratios
reported from customers are in an average a factor of 2 to 5; this means
that a table that formerly allocated 500 GB of disk space is now reduced
to a size of 100 GB for a compression factor of five.
The data in the SALES fact table is artificially
generated and does not show the typical "natural sorting" you
will find in any data warehouse environment where the data was cleansed,
consolidated, or even aggregated prior to its INSERT.
|
Back to Topic List
Many data warehouses maintain a rolling window of data. For example, the data warehouse stores the data from the most recent 12 months of sales. Just as a new partition can be added to the SALES table, an old partition can be quickly (and independently) removed from the SALES table. Partitioning provides the ideal framework for those operations. The two benefits (reduced resources utilization and minimal end user impact) are just as pertinent to removing a partition as they are to adding a partition.
Perform the Steps of the Rolling Windows Operation:
To perform the steps of the rolling windows operation and
learn about enhancements in Oracle Database 10g, perform the following
steps:
1. Prepare a Stand-Alone
Table with the New Data
To perform the rolling window operation, you need to create and load a stand-alone table with the new data by performing the following steps. Note that you are going to use the external table you already defined, but pointing to a different external file:
1.1 Modify the External Table to
DBMS_STATS the Sales Q1 Data
In this section, you use the external table you already defined. However, this time you will use a different external file, the sales Q1 data. So, you have to modify the location attribute of the external table to point to the new data file.
| 1. |
First, check again the number of rows in the current external table. (Note that you can run the select_et.sql script file.)
@select_et.sql
SELECT COUNT(*) FROM sales_delta_xt;
SELECT MAX(time_id) FROM sales_delta_xt;

The file, containing all sales transactions for December 2001, will show
30-DEC-2001. You will see that the number of rows and the MAX(time_id)
will be different after changing the external file on OS level.
|
| 2. |
Change the LOCATION attribute:
@alter_loc_attrib.sql
ALTER TABLE sales_delta_xt location ( 'salesQ1.dat' );
Then check the new data:
@select_et.sql
SELECT COUNT(*) FROM sales_delta_xt;
SELECT MAX(time_id) FROM sales_delta_xt;
The number of rows as well as the maximum TIME_ID have changed. If your external
table file is correct, the maximum TIME_ID is 30-MAR-2002.

|
Back to List
1.2 Create the Table for the New
Sales Q1 Data:
You will create an empty table for the new sales Q1 data. This table will be added to the already existing partitioned SALES table later.
| 1. |
Run the SQL to create the table:
@create_stage_table.sql
DROP TABLE sales_delta;
CREATE TABLE sales_delta NOLOGGING NOCOMPRESS
AS SELECT * FROM sales WHERE 1=0; 
|
Back to List
1.3 Load This Table
To load this table, you perform the following steps:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following statement:
@load_stage_table2.sql
INSERT /*+ APPEND */ INTO sales_delta SELECT /*+ PARALLEL (SALES_DELTA_XT,4) */ PROD_ID, CUST_ID, TIME_ID, case CHANNEL_ID when 'S' then 3 when 'T' then 9 when 'C' then 5 when 'I' then 4 when 'P' then 2 else 99 end, PROMO_ID, sum(QUANTITY_SOLD) quantity_sold, sum(AMOUNT_SOLD) amount_sold FROM SALES_DELTA_XT GROUP BY prod_id,time_id,cust_id,channel_id,promo_id;
COMMIT; 
After loading the sales_delta table, gather statistics for this newly created table.
|
| 2. |
From a SQL*Plus session logged on to the SH schema, execute the following statement:
@gather_stat_stage_table.sql
Rem gather statistics for the table exec dbms_stats.gather_table_stats('SH','sales_delta',estimate_percent=>20); 
|
Back to List
1.4 Create Bitmap Indexes for This
Table
Because you are going to exchange this stand-alone table with an empty partition of the SALES table at a later point in time, you have to build exactly the same index structure as the existing SALES table to keep the local index structures of this particular table in a usable state after the exchange.
| 1. |
Before creating any bitmap indexes, you need to modify the newly created
table to DBMS_STATS the same internal property as a compressed table without
actually compressing any data. This operation is necessary to create bitmap
indexes that are valid to be exchanged into the partitioned table that
already contains compressed partitions.
@alter_sales_delta.sql
ALTER TABLE sales_delta COMPRESS;
ALTER TABLE sales_delta NOCOMPRESS;
|
| 2. |
From a SQL*Plus session logged on to the SH schema, execute the following statements:
@create_static_bitmap_index.sql
CREATE BITMAP INDEX sales_prod_local_bix
ON sales_delta (prod_id)
NOLOGGING COMPUTE STATISTICS ;
CREATE BITMAP INDEX sales_cust_local_bix
ON sales_delta (cust_id)
NOLOGGING COMPUTE STATISTICS ;
CREATE BITMAP INDEX sales_time_local_bix
ON sales_delta (time_id)
NOLOGGING COMPUTE STATISTICS ;
CREATE BITMAP INDEX sales_channel_local_bix
ON sales_delta (channel_id)
NOLOGGING COMPUTE STATISTICS ;
CREATE BITMAP INDEX sales_promo_local_bix
ON sales_delta (promo_id)
NOLOGGING COMPUTE STATISTICS ;
Note that the statistics for those indexes will be created as part of the index creation.

|
Back to List
1.5 Create Constraints for This
Table:
The same is true for the existing constraints of the SALES table.
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@create_constraints.sql set echo on
Rem *****
ALTER TABLE channels MODIFY CONSTRAINT CHANNELS_PK RELY; ALTER TABLE countries MODIFY CONSTRAINT COUNTRIES_PK RELY; ALTER TABLE customers MODIFY CONSTRAINT CUSTOMERS_PK RELY; ALTER TABLE products MODIFY CONSTRAINT PRODUCTS_PK RELY; ALTER TABLE promotions MODIFY CONSTRAINT PROMO_PK RELY; ALTER TABLE times MODIFY CONSTRAINT TIMES_PK RELY;
ALTER TABLE sales_delta
ADD ( CONSTRAINT sales_product_delta_fk
FOREIGN KEY (prod_id)
REFERENCES products RELY ENABLE NOVALIDATE
, CONSTRAINT sales_customer_delta_fk
FOREIGN KEY (cust_id)
REFERENCES customers RELY ENABLE NOVALIDATE
, CONSTRAINT sales_time_delta_fk
FOREIGN KEY (time_id)
REFERENCES times RELY ENABLE NOVALIDATE
, CONSTRAINT sales_channel_delta_fk
FOREIGN KEY (channel_id)
REFERENCES channels RELY ENABLE NOVALIDATE
, CONSTRAINT sales_promo_delta_fk
FOREIGN KEY (promo_id)
REFERENCES promotions RELY ENABLE NOVALIDATE
) ; 
|
Back to List
Back to Topic
2. Add the New Data to the Fact Table
The next task in performing a rolling window operation is to add the newly loaded and indexed data to the fact table. To do this, you perform the following steps:
2.1 Create a New Partition
You need to create a new, empty partition. You can either
create the new partition with a distinct upper boundary or by choosing the keyword
MAXVALUE. The latter option ensures that records violating the potential upper
boundary condition are not rejected and the INSERT operation succeeds.
In this business scenario, you issue a SPLIT PARTITION after
the loading operation to identify any potential violations. All records violating
the upper boundary will be "separated" into an extra partition.
You need to create a new, empty partition. To do this, perform
the following step:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@create_partition_for_sales.sql
Rem *****
Rem current partitions
Rem *****
select partition_name, high_value
from user_tab_partitions
where table_name='SALES'
order by partition_position;
Rem *****
Rem create ADDITIONAL PARTITION on sales
Rem *****
ALTER TABLE sales
ADD PARTITION sales_q1_2002
VALUES LESS THAN (MAXVALUE);
Rem *****
Rem what is in the partition now?
Rem empty
Rem *****
SELECT COUNT(*)
FROM sales PARTITION (sales_q1_2002);

|
Back to List
2.2 Exchange the Partition
You are now ready to add the newly loaded and indexed data
to the real sales fact table
by performing a PARTITION EXCHANGE
command. Note that this is only a DDL command, which doesn't touch the actual
data at all.. To do this, you perform the following step:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@exchange_partition_wo_gim.sql
Rem *****
Rem EXCHANGE IT
Rem *****
ALTER TABLE sales EXCHANGE PARTITION sales_q1_2002 WITH TABLE sales_delta INCLUDING INDEXES; 
|
Back to List
2.3 Select From the Partition
Now you can select from the newly added and exchanged partition to experience how fast you inserted thousands of rows.
Note that the more data you have to add to your partitioned fact table, the more time youre saving with this metadata-only operation, and the more you will experience the benefit of minimal to zero user impact.
You need a logical partitioning such as RANGE Partitioning; HASH Partitioning cannot be used for the very common Rolling Window Operation.
All indexes of the SALES table are maintained and usable.
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following queries. This will show you the number of rows in the exchanged partition and the stand-alone table (which is now empty):
@select_count.sql
SELECT COUNT(*) FROM sales PARTITION (sales_q1_2002);
SELECT COUNT(*) FROM sales_delta;
Have you ever inserted that many rows that quickly?
|
| 2. |
Note that all local indexes of the SALES table are valid.
@show_sales_idx_status.sql
PROMPT INDEXES ARE MAINTAINED
SELECT ui.index_name, DECODE(uip.status,null,ui.status,uip.status) FROM user_ind_partitions uip, user_indexes ui WHERE ui.index_name=uip.index_name(+) AND ui.table_name='SALES' GROUP BY ui.index_name, DECODE(uip.status,null,ui.status,uip.status);

You can also use the WITHOUT VALIDATION clause as part of the PARTITION EXCHANGE command. Oracle will then suppress the validity checking of the table that is going to be exchanged; otherwise Oracle guarantees that all values of the partition key match within the partition boundaries.
|
Back to List
2.4 Split the Most Recent Partition
to Ensure (Business) Data Integrity
As mentioned before, you decided to load the data into a partition
with no fixed upper boundary to avoid any potential errors. To identify any potential
violation, you will split the most recent partition, thus creating two partitions,
one with a fixed upper boundary (DBMS_STATSing the business rule).
Beginning with Oracle9i
Release 2, Oracle is using an enhanced fast-split operation, where the RDBMS
detects whether or not one of the two new partitions after a SPLIT operation
will be empty. If this is the case, Oracle will not create two new segments;
it will only create the one DBMS_STATSing the new empty partition, and it will
use the existing segment as the new partition containing all the data.
This optimization is absolutely transparent. It improves the run time of a SPLIT operation, saves system resources, and does not require any index maintenance.
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following queries:
@fast_split_sales.sql
ALTER TABLE sales SPLIT PARTITION sales_q1_2002 AT (TO_DATE('01-APR-2002','DD-MON-YYYY')) INTO (PARTITION sales_q1_2002, PARTITION sales_beyond_q1_2002); PROMPT Let's control the count in the most recent partition. should be empty
SELECT COUNT(*) FROM sales PARTITION (sales_beyond_q1_2002); PROMPT INDEXES ARE MAINTAINED
Rem since no data is moved with the SPLIT operation, the indexes are still valid
SELECT ui.index_name, DECODE(uip.status,null,ui.status,uip.status)
FROM user_ind_partitions uip, user_indexes ui
WHERE ui.index_name=uip.index_name(+)
AND ui.table_name='SALES'
GROUP BY ui.index_name, DECODE(uip.status,null,ui.status,uip.status);
PROMPT Now you can drop the empty overflow partition
ALTER TABLE sales DROP PARTITION sales_beyond_q1_2002;
|
| 2. |
Note that all local indexes of the SALES table are still valid.
@show_sales_idx_status.sql  |
Back to List
Back to Topic
3. Delete Old Data from the Fact Table
The next task to performing a Rolling Window Operation is to delete old data from the fact table. You only want to analyze the most recent data of the last three years. Therefore, because you added Q1-2002, you have to delete the data of Q1-1998.
Without Range Partitioning, you have to perform a DML operation against the table. With partitioning, you can leverage the PARTITION EXCHANGE command again to remove the data from the fact table. Like for the adding of new data, Hash Partitioning does not help you here either.
Note that you're not deleting the data. Instead you are exchanging (logically replacing) the partition containing this data from the SALES fact table with an empty stand-alone table with the same logical structure. You can then archive this data or drop the exchanged partition, depending on your business needs.
3.1 Create an Empty Stand-Alone Table
You need to create an empty table in which to store the old 1998 data.
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following statements:
@create_empty_sat.sql
DROP TABLE sales_old_q1_1998;
CREATE TABLE sales_old_q1_1998 NOLOGGING COMPRESS AS SELECT * FROM sales WHERE 1=0; 
|
Back to List
3.2 Create Bitmap Indexes for This
Table:
Now create the local indexes.
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@create_ndx.sql
set echo on
Rem ***** PROMPT CREATE LOCAL INDEXES Rem ***** CREATE BITMAP INDEX sales_prod_old_bix ON sales_old_q1_1998 (prod_id) NOLOGGING COMPUTE STATISTICS ; CREATE BITMAP INDEX sales_cust_old_bix ON sales_old_q1_1998 (cust_id) NOLOGGING COMPUTE STATISTICS ; CREATE BITMAP INDEX sales_time_old_bix ON sales_old_q1_1998 (time_id) NOLOGGING COMPUTE STATISTICS ; CREATE BITMAP INDEX sales_channel_old_bix ON sales_old_q1_1998 (channel_id) NOLOGGING COMPUTE STATISTICS ; CREATE BITMAP INDEX sales_promo_old_bix ON sales_old_q1_1998 (promo_id) NOLOGGING COMPUTE STATISTICS ; 
|
Back to List
3.3 Create Constraints for This
Table
Now create the constraints.
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@create_constraints_old.sql
Rem ***10g*** REM TO DO: cannot enable fk rely when pk is set to norely Rem *****
ALTER TABLE channels MODIFY CONSTRAINT CHANNELS_PK RELY; ALTER TABLE countries MODIFY CONSTRAINT COUNTRIES_PK RELY; ALTER TABLE customers MODIFY CONSTRAINT CUSTOMERS_PK RELY; ALTER TABLE products MODIFY CONSTRAINT PRODUCTS_PK RELY; ALTER TABLE promotions MODIFY CONSTRAINT PROMO_PK RELY; ALTER TABLE times MODIFY CONSTRAINT TIMES_PK RELY;
ALTER TABLE sales_old_q1_1998 ADD ( CONSTRAINT sales_product_old_fk FOREIGN KEY (prod_id) REFERENCES products RELY ENABLE NOVALIDATE , CONSTRAINT sales_customer_old_fk FOREIGN KEY (cust_id) REFERENCES customers RELY ENABLE NOVALIDATE , CONSTRAINT sales_time_old_fk FOREIGN KEY (time_id) REFERENCES times RELY ENABLE NOVALIDATE , CONSTRAINT sales_channel_old_fk FOREIGN KEY (channel_id) REFERENCES channels RELY ENABLE NOVALIDATE , CONSTRAINT sales_promo_old_fk FOREIGN KEY (promo_id) REFERENCES promotions RELY ENABLE NOVALIDATE ) ; 
|
Back to List
3.4 Show the Data in the Partition
Before the Exchange
Before you perform the exchange, you will want to take a look at the 1998 Q1 data that will be aged out of the partition.
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@show_partition.sql
set echo on
Rem *****
Rem show the actual content of the partition to be aged out
Rem BEFORE exchange
Rem *****
SELECT COUNT(*)
FROM sales PARTITION (sales_q1_1998); 
|
Back to List
3.5 Exchange the Partition
You now need to exchange the empty table with the existing Q1-1998 partition. To do this, you perform the following step:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following SQL statement:
@exchange_old_partition.sql
Rem ***** PROMPT EXCHANGE IT TO "REMOVE" OLD DATA Rem the exchange happens in a fraction of seconds, Rem since it is only a DDL command Rem *****
ALTER TABLE sales
EXCHANGE PARTITION sales_q1_1998
WITH TABLE sales_old_q1_1998
INCLUDING INDEXES; 
Note that you could have used a DROP PARTITION instead. Table sales_old_q1_1998 now stores all the data of the first quarter of 1998. You could drop this table to remove the data entirely from the system. |
Back to List
3.6 Show the Data in the Partition
After the Exchange:
After you perform the exchange, you will want to take a look at the data in the partition.
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following queries:
@count_sales.sql
PROMPT show the actual content of the partition to be aged out AFTER exchange Rem no more data in it SELECT COUNT(*)
FROM sales PARTITION (sales_q1_1998);
SELECT COUNT(*)
FROM sales_old_q1_1998; 
Unlike before the EXCHANGE command, the stand-alone table now stores thousands of rows whereas the equivalent partition of SALES is empty.
|
| 2. |
Local Indexes are not affected.
@show_sales_idx_status.sql
PROMPT INDEXES ARE MAINTAINED
SELECT ui.index_name, DECODE(uip.status,null,ui.status,uip.status) FROM user_ind_partitions uip, user_indexes ui WHERE ui.index_name=uip.index_name(+) AND ui.table_name='SALES' GROUP BY ui.index_name, DECODE(uip.status,null,ui.status,uip.status); 
|
Back to List
Back to Topic
4. Oracle 10g Enhancements
for Local Index Maintenance:
Beginning with Oracle10g, all Partition Maintenance Operations can be executed without any impact on its availability. Local Index Maintenance allows you to keep local indexes of a partitioned table up-to-date as part of
any atomic Partition Maintenance operation, thus making no indexes unusable and affecting its usage when the maintenance operation takes place.
Oracle extended the SQL syntax for Partition Maintenance Operations to control the physical attributes, such as index placement, for all affected local index structures.
Steps:
After successfully loading the data for the first quarter of 2002, the DBA team recognized that due to changing business requirements the query pattern has changed; instead of being focused mostly on quarterly analysis, many
business users have started to rely on monthly reporting and analysis.
To address this changed business requirement and to optimize the query performance, the DBA team decided to leverage Oracle Partitioning and to split the most recent quarter into monthly partitions. This must be
accomplished without any impact on online availability.
The online availability for local index maintenance will not be demonstrated explicitly; online availability is demonstrated for global index maintenance and works in exactly the same manner for local indexes.
4.1 Split the Most Recent Partition
You need to create a new, empty partition. You can either
create the new partition with a distinct upper boundary or by choosing the keyword
MAXVALUE. The latter option ensures that records violating the potential upper
boundary condition is not rejected and the INSERT operation succeeds.
In this business scenario, a SPLIT PARTITION is issued after the loading operation to identify any potential violations. All records violating the upper boundary will be "separated" into an extra partition.
You need to create a new, empty partition. To do this, you perform the following steps:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following SQL statement to split off the most recent month (March 2002) from the quarterly partition, including local index maintenance, split1_10g.sql:
@split1_10g.sql
PROMPT Now leverage the new functionality and break down the existing PROMPT quarter partition into monthly ones, leveraging new 10g functionality Rem note that an index location is not specified, so they will be colocated with the partitions.
Rem the name will be inherited by the partition name
ALTER TABLE sales SPLIT PARTITION sales_q1_2002
AT (TO_DATE('01-MAR-2002','DD-MON-YYYY'))
INTO (PARTITION sales_1_2_2002 TABLESPACE example,
PARTITION sales_MAR_2002 TABLESPACE example NOCOMPRESS)
UPDATE INDEXES; 
|
| 2. |
You can see that the new index partitions are colocated with the table partitions and that the index partition naming is inherited from the partition naming.
@see_split.sql
COL segment_name format a25
COL partition_name format a25
COL tablespace_name format a25
Rem you will see that the newly created index segments Rem are co-located with the table partitions
Rem Also note the automatically derived naming
SELECT segment_name, partition_name, tablespace_name
FROM user_segments
WHERE segment_type='INDEX PARTITION'
AND segment_name IN
(SELECT index_name
FROM user_indexes
WHERE table_name='SALES'); 
|
Back to List
4.2 Split a Partition Using the
Extended SQL Syntax
You are also going to split the remainder of the former quarter partition into a January and February partition. For demonstration purposes, create one of the new partitions in the SYSAUX tablespace and name
some of the indexes explicitly.
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following SQL statement to split the remainder partition, including local index maintenance, split2_10g.sql:
@split2_10g.sql
ALTER TABLE sales SPLIT PARTITION sales_1_2_2002 AT (TO_DATE('01-FEB-2002','DD-MON-YYYY')) INTO (PARTITION sales_JAN_2002 TABLESPACE sysaux COMPRESS, PARTITION sales_FEB_2002 TABLESPACE example NOCOMPRESS) UPDATE INDEXES (sales_time_bix (PARTITION jan_02 TABLESPACE example, PARTITION feb_02 TABLESPACE system)); 
|
| 2. |
You can see that the new index partitions are colocated with the table partitions and that the index partition naming is inherited from the partition naming.
@see_split2.sql
SELECT segment_name, partition_name, tablespace_name FROM user_segments WHERE segment_type='INDEX PARTITION' AND tablespace_name <>'EXAMPLE' AND segment_name IN (SELECT index_name FROM user_indexes WHERE table_name='SALES'); 
|
Back to List
4.3 Clean Up
Let's now clean up, get
out of the SYSAUX tablespace, and back to the standard naming (conventions).
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@cleanup_split_10g.sql
PROMPT bring them back in shape (into tablespace EXAMPLE)
ALTER TABLE sales MOVE PARTITION sales_JAN_2002 TABLESPACE example COMPRESS UPDATE INDEXES (sales_time_bix (PARTITION sales_jan_2002 TABLESPACE example), sales_cust_bix (PARTITION sales_jan_2002 TABLESPACE example), sales_channel_bix (PARTITION sales_jan_2002 TABLESPACE example), sales_prod_bix (PARTITION sales_jan_2002 TABLESPACE example), sales_promo_bix (PARTITION sales_jan_2002 TABLESPACE example)) ; PROMPT Let's move the last cowboy back to EXAMPLE ..
ALTER INDEX sales_time_bix
REBUILD PARTITION feb_02 TABLESPACE example; PROMPT no index structures outside EXAMPLE anymore
SELECT segment_name, partition_name, tablespace_name
FROM user_segments
WHERE segment_type='INDEX PARTITION'
AND segment_name IN (SELECT index_name
FROM user_indexes
WHERE table_name='SALES')
AND tablespace_name <> 'EXAMPLE'; 
|
Back to List
Back to Topic
5. Utilize Oracle's Global Index
Maintenance:
Global Index Maintenance enables you to keep global indexes of a partitioned table up-to-date as part of any atomic Partition Maintenance operation, thus making no global indexes unusable and not affecting their usage when the maintenance operation takes place.
Steps:
5.1 Prepare for Global Index Maintenance
Exchange the March data into the partitioned table in the presence of a global index. First, you have to build the necessary infrastructure first:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@prep4_global_index.sql
Rem PREPARE FOR GLOBAL INDEX MAINTENANCE CREATE TABLE sales_mar_2002_temp NOLOGGING AS SELECT * FROM sales PARTITION (sales_MAR_2002); ALTER TABLE sales TRUNCATE PARTITION sales_MAR_2002; Rem control ...
Rem is empty SELECT COUNT(*)
FROM sales PARTITION (sales_MAR_2002);
ALTER TABLE sales_mar_2002_temp COMPRESS;
ALTER TABLE sales_mar_2002_temp NOCOMPRESS;
CREATE BITMAP INDEX sales_prod_mar_2002_bix
ON sales_mar_2002_temp (prod_id)
NOLOGGING COMPUTE STATISTICS ;
CREATE BITMAP INDEX sales_cust_mar_2002_bix
ON sales_mar_2002_temp (cust_id)
NOLOGGING COMPUTE STATISTICS ;
CREATE BITMAP INDEX sales_time_mar_2002_bix
ON sales_mar_2002_temp (time_id)
NOLOGGING COMPUTE STATISTICS ;
CREATE BITMAP INDEX sales_channel_mar_2002_bix
ON sales_mar_2002_temp (channel_id)
NOLOGGING COMPUTE STATISTICS ;
CREATE BITMAP INDEX sales_promo_mar_2002_bix
ON sales_mar_2002_temp (promo_id)
NOLOGGING COMPUTE STATISTICS ; 

|
Back to List
5.2 Build a Global Index
To demonstrate the global index maintenance functionality, you first need to create a global index.. To do this, you perform the following steps:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@create_global_index.sql
CREATE UNIQUE INDEX sales_pk
ON sales (prod_id, cust_id, promo_id, channel_id, time_id)
NOLOGGING COMPUTE STATISTICS; This may take up to a minute.

|
| 2. |
Let's build a constraint leveraging this index. Execute the following statements:
@add_sales_pk.sql
ALTER TABLE sales ADD CONSTRAINT sales_pk
PRIMARY KEY (prod_id, cust_id, promo_id, channel_id, time_id) USING INDEX;
|
| 3. |
Note that if a constraint is defined using the global index, the same constraint must be defined for the table to be exchanged as well!
@add_salestemp_pk
ALTER TABLE sales_mar_2002_temp ADD CONSTRAINT sales_mar_2002_temp_pk PRIMARY KEY (prod_id, cust_id, promo_id, channel_id, time_id) DISABLE VALIDATE; |
Back to List
5.3 Exchange a Partition with Global
Index Maintenance
To demonstrate the impact of a partition maintenance operation
on concurrent online access, you will need TWO sessions and therefore TWO windows.
Please read the following section CAREFULLY before exercising.
In Window One, you will perform the following:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@use_global_index.sql
Rem STATEMENT WHICH USES GLOBAL INDEX - Window 1
EXPLAIN PLAN FOR
SELECT /*+ INDEX(sales, sales_pk) */ count(*)
FROM sales
WHERE prod_id BETWEEN 100 AND 500;
set linesize 140
SELECT *
FROM TABLE(dbms_xplan.display); After verifying the plan that you are using the global index, process this statement over and over again. You can use the SQL*Plus functionality "r" to rerun the last statement.
@run_select.sql
SELECT /*+ INDEX(sales, sales_pk) */ count(*)
FROM sales
WHERE prod_id BETWEEN 100 AND 500; 
 While youre running the query, perform the steps below in Window two. You will see that there is no impact on concurrent query access using a global index when the partition maintenance operation takes place.
The query will not fail. You will also recognize that the query result will change as soon as the partition exchange command succeeded. Oracle guarantees READ CONSISTENCY in this situation as well and provides most efficient partition table and index maintenance operations without restricting the online usage. |
In Window Two, perform the following:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@exchange_partition_w_gim.sql
set echo on
Rem *****
Rem EXCHANGE IT
Rem - with global index maintenance
Rem demonstrate influence of index on other queries in
Rem second window
Rem *****
ALTER TABLE sales
EXCHANGE PARTITION sales_mar_2002 WITH TABLE sales_mar_2002_temp INCLUDING INDEXES UPDATE GLOBAL INDEXES; 
Although it is a DDL command, it might take some time because the global indexes are maintained as part of the atomic PARTITION EXCHANGE command.
|
| 2. |
You will see that all indexes are still valid after the partition maintenance operation:
@show_sales_idx_status.sql
SELECT ui.index_name,
DECODE(uip.status,null,ui.status,uip.status) status,
count(*) num_of_part
FROM user_ind_partitions uip, user_indexes ui
WHERE ui.index_name=uip.index_name(+)
AND ui.table_name='SALES'
GROUP BY ui.index_name,
DECODE(uip.status,null,ui.status,uip.status);
|
| 3. |
Lets see whats in the exchanged partition and the stand-alone table:
@count_mar_sales.sql
SELECT COUNT(*)
FROM sales PARTITION (sales_mar_2002);
SELECT COUNT(*)
FROM sales_mar_2002_temp;

Thousands of rows were added to the partitioned table with this command; the stand-alone table is empty now.
|
The new sales data of Q1-2001 is exchanged again. The global index was maintained as part of the PARTITION EXCHANGE command, thus not affecting any online usage.
Now you will investigate the old behavior prior to Oracle9i without global index maintenance.
Back to List
5.4 Exchange a Partition Without
Global Index Maintenance
To demonstrate this functionality, you will need TWO windows. Please read the following section CAREFULLY before exercising it by yourself.
In Window One, you will perform the following:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@use_global_index.sql
Rem STATEMENT WHICH USES GLOBAL INDEX - Window 1
explain plan for
SELECT /*+ INDEX(sales, sales_pk) */ count(*)
FROM sales
WHERE prod_id BETWEEN 100 AND 500;
set linesize 140
SELECT *
FROM table(dbms_xplan.display);
SELECT /*+ INDEX(sales, sales_pk) */ count(*)
FROM sales
WHERE prod_id BETWEEN 100 AND 500; Perform the steps in Window two below and then execute the query above to see the difference.
It will fail as soon as the partition maintenance command is processed. 
|
In Window Two, perform the following:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query:
@exchange_partition_wo_gim2.sql
Rem EXCHANGE IT
Rem - without global index maintenance
Rem demonstrate influence of index on other queries in
Rem second window
Rem *****
ALTER TABLE sales
EXCHANGE PARTITION sales_mar_2002
WITH TABLE sales_mar_2002_temp INCLUDING INDEXES; 
|
| 2. |
You will see that the global index is now marked unusable.
@show_sales_idx_status.sql
SELECT ui.index_name,
DECODE(uip.status,null,ui.status,uip.status) status,
count(*) num_of_part
FROM user_ind_partitions uip, user_indexes ui
WHERE ui.index_name=uip.index_name(+)
AND ui.table_name='SALES'
GROUP BY ui.index_name, DECODE(uip.status,null,ui.status,uip.status);
|
Back to List
5.5 Drop Global Index and Exchange
Back
To clean up your environment you will need to perform the following:
| 1. |
From a SQL*Plus session logged on to the SH schema, execute the following query for module-specific modifications:
@cleanup_mod1.sql
Rem drop unique index sales_pk. you do not need it, and it Rem might cause some problems later on with DML operations Rem *****
ALTER TABLE sales DROP CONSTRAINT sales_pk; DROP INDEX sales_pk;
Rem Cleanup - bring data back
Rem Necessary - that way you do not have to check for it at the end - top goal is no
Rem interference with the original SH schema ...
Rem - data for 1998
Rem - no data for 2001
ALTER TABLE sales
EXCHANGE PARTITION sales_q1_1998
WITH TABLE sales_old_q1_1998 INCLUDING INDEXES;
ALTER TABLE sales DROP PARTITION sales_jan_2002;
ALTER TABLE sales DROP PARTITION sales_feb_2002;
ALTER TABLE sales DROP PARTITION sales_mar_2002;
DROP TABLE sales_mar_2002_temp;
DROP TABLE sales_delta;
DROP TABLE sales_old_q1_1998;
PROMPT original situation again
Rem SELECT COUNT(*) FROM sales PARTITION (sales_q1_2001);
Rem SELECT COUNT(*) FROM sales PARTITION (sales_q1_1998); PROMPT just to be safe ... -cleanup module
set serveroutput on
exec dw_handsOn.cleanup_modules PROMPT control whether the correction script was applied properly ..
SELECT * FROM TABLE(dw_handsOn.verify_env)
Please ensure that the environment is properly reset. Having an environment that is not "clean" may affect the usage or run-time behavior in other tutorials. |
Back to List
Back to Topic
In this tutorial, you've learned how to:
 |
Load data using external tables |
 |
Compare usage of SQL*Loader to external tables |
 |
Perform a table compression to save disk
space |
 |
Perform a rolling window operation using
Oracle partitioning |
Back to Topic List
 |
To learn more about
Oracle Database 10g, refer to additional OBEs on the OTN
Web site. |
 |
To learn more about
other Business Intelligence products, refer to the BI OBEs located here. |
 |
To ask a question about this OBE tutorial,
post a query on the OBE
Discussion Forum |
Back to Topic List
Place the cursor over this icon to hide all screenshots.
|